diff --git a/packages/flutter/lib/src/widgets/nested_scroll_view.dart b/packages/flutter/lib/src/widgets/nested_scroll_view.dart index 2711e0b624..73e242e3d1 100644 --- a/packages/flutter/lib/src/widgets/nested_scroll_view.dart +++ b/packages/flutter/lib/src/widgets/nested_scroll_view.dart @@ -1914,6 +1914,15 @@ class RenderSliverOverlapInjector extends RenderSliver { void performLayout() { _currentLayoutExtent = handle.layoutExtent; _currentMaxExtent = handle.layoutExtent; + assert( + _currentLayoutExtent != null && _currentMaxExtent != null, + 'SliverOverlapInjector has found no absorbed extent to inject.\n ' + 'The SliverOverlapAbsorber must be an earlier descendant of a common ' + 'ancestor Viewport, so that it will always be laid out before the ' + 'SliverOverlapInjector during a particular frame.\n ' + 'The SliverOverlapAbsorber is typically contained in the list of slivers ' + 'provided by NestedScrollView.headerSliverBuilder.\n' + ); final double clampedLayoutExtent = math.min( _currentLayoutExtent! - constraints.scrollOffset, constraints.remainingPaintExtent, diff --git a/packages/flutter/test/widgets/nested_scroll_view_test.dart b/packages/flutter/test/widgets/nested_scroll_view_test.dart index c418746510..0871c32e29 100644 --- a/packages/flutter/test/widgets/nested_scroll_view_test.dart +++ b/packages/flutter/test/widgets/nested_scroll_view_test.dart @@ -2908,6 +2908,46 @@ void main() { // Restructuring inner scrollable while scroll is in progress shouldn't crash. await tester.pumpWidget(buildApp(nested: true)); }); + + testWidgets('SliverOverlapInjector asserts when there is no SliverOverlapAbsorber', (WidgetTester tester) async { + Widget buildApp() { + return MaterialApp( + home: Scaffold( + body: NestedScrollView( + headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { + return [ + const SliverAppBar(), + ]; + }, + body: Builder( + builder: (BuildContext context) { + return CustomScrollView( + slivers: [ + SliverOverlapInjector( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + ), + ], + ); + } + ), + ), + ), + ); + } + final List exceptions = []; + final FlutterExceptionHandler? oldHandler = FlutterError.onError; + FlutterError.onError = (FlutterErrorDetails details) { + exceptions.add(details.exception); + }; + await tester.pumpWidget(buildApp()); + FlutterError.onError = oldHandler; + expect(exceptions.length, 4); + expect(exceptions[0], isAssertionError); + expect( + (exceptions[0] as AssertionError).message, + contains('SliverOverlapInjector has found no absorbed extent to inject.'), + ); + }); } double appBarHeight(WidgetTester tester) => tester.getSize(find.byType(AppBar, skipOffstage: false)).height;