diff --git a/packages/flutter/lib/src/widgets/page_view.dart b/packages/flutter/lib/src/widgets/page_view.dart index 4174748fdf..9ed9a40cfd 100644 --- a/packages/flutter/lib/src/widgets/page_view.dart +++ b/packages/flutter/lib/src/widgets/page_view.dart @@ -185,11 +185,22 @@ class PageController extends ScrollController { return position.page; } + bool _debugCheckPageControllerAttached() { + assert(positions.isNotEmpty, 'PageController is not attached to a PageView.'); + assert( + positions.length == 1, + 'Multiple PageViews are attached to ' + 'the same PageController.', + ); + return true; + } + /// Animates the controlled [PageView] from the current page to the given page. /// /// The animation lasts for the given duration and follows the given curve. /// The returned [Future] resolves when the animation completes. Future animateToPage(int page, {required Duration duration, required Curve curve}) { + assert(_debugCheckPageControllerAttached()); final _PagePosition position = this.position as _PagePosition; if (position._cachedPage != null) { position._cachedPage = page.toDouble(); @@ -213,6 +224,7 @@ class PageController extends ScrollController { /// Jumps the page position from its current value to the given value, /// without animation, and without checking if the new value is in range. void jumpToPage(int page) { + assert(_debugCheckPageControllerAttached()); final _PagePosition position = this.position as _PagePosition; if (position._cachedPage != null) { position._cachedPage = page.toDouble(); @@ -332,6 +344,7 @@ class _PagePosition extends ScrollPositionWithSingleContext implements PageMetri final int initialPage; double _pageToUseOnStartup; + // When the viewport has a zero-size, the `page` can not // be retrieved by `getPageFromPixels`, so we need to cache the page // for use when resizing the viewport to non-zero next time. @@ -362,6 +375,7 @@ class _PagePosition extends ScrollPositionWithSingleContext implements PageMetri @override double get viewportFraction => _viewportFraction; double _viewportFraction; + set viewportFraction(double value) { if (_viewportFraction == value) { return; diff --git a/packages/flutter/test/widgets/page_view_test.dart b/packages/flutter/test/widgets/page_view_test.dart index 80fdb9b5bc..9559426ef9 100644 --- a/packages/flutter/test/widgets/page_view_test.dart +++ b/packages/flutter/test/widgets/page_view_test.dart @@ -1403,6 +1403,163 @@ void main() { }); }); + group('Asserts in jumpToPage and animateToPage methods works properly', () { + Widget createPageView([PageController? controller]) { + return MaterialApp( + home: Scaffold( + body: PageView( + controller: controller, + children: [ + Container(color: Colors.red), + Container(color: Colors.green), + Container(color: Colors.blue), + ], + ), + ), + ); + } + + group('One pageController is attached to multiple PageViews', () { + Widget createMultiplePageViews(PageController controller) { + return MaterialApp( + home: Scaffold( + body: Column( + children: [ + Expanded( + child: PageView( + controller: controller, + children: [ + Container(color: Colors.red), + Container(color: Colors.green), + Container(color: Colors.blue), + ], + ), + ), + Expanded( + child: PageView( + controller: controller, + children: [ + Container(color: Colors.orange), + Container(color: Colors.purple), + Container(color: Colors.yellow), + ], + ), + ), + ], + ), + ), + ); + } + + testWidgets( + 'animateToPage assertion is working properly when pageController is attached to multiple PageViews', + (WidgetTester tester) async { + final PageController controller = PageController(); + addTearDown(controller.dispose); + await tester.pumpWidget(createMultiplePageViews(controller)); + + expect( + () => controller.animateToPage( + 2, + duration: const Duration(milliseconds: 300), + curve: Curves.ease, + ), + throwsA( + isAssertionError.having( + (AssertionError error) => error.message, + 'message', + equals( + 'Multiple PageViews are attached to ' + 'the same PageController.', + ), + ), + ), + ); + }, + ); + + testWidgets( + 'jumpToPage assertion is working properly when pageController is attached to multiple PageViews', + (WidgetTester tester) async { + final PageController controller = PageController(); + addTearDown(controller.dispose); + await tester.pumpWidget(createMultiplePageViews(controller)); + + expect( + () => controller.jumpToPage(2), + throwsA( + isAssertionError.having( + (AssertionError error) => error.message, + 'message', + equals( + 'Multiple PageViews are attached to ' + 'the same PageController.', + ), + ), + ), + ); + }, + ); + }); + + group('PageController is attached or is not attached to PageView', () { + testWidgets('Assert behavior of animateToPage works properly', (WidgetTester tester) async { + final PageController controller = PageController(); + addTearDown(controller.dispose); + + // pageController is not attached to PageView + await tester.pumpWidget(createPageView()); + expect( + () => controller.animateToPage( + 2, + duration: const Duration(milliseconds: 300), + curve: Curves.ease, + ), + throwsA( + isAssertionError.having( + (AssertionError error) => error.message, + 'message', + equals('PageController is not attached to a PageView.'), + ), + ), + ); + + // pageController is attached to PageView + await tester.pumpWidget(createPageView(controller)); + expect( + () => controller.animateToPage( + 2, + duration: const Duration(milliseconds: 300), + curve: Curves.ease, + ), + returnsNormally, + ); + }); + + testWidgets('Assert behavior of jumpToPage works properly', (WidgetTester tester) async { + final PageController controller = PageController(); + addTearDown(controller.dispose); + + // pageController is not attached to PageView + await tester.pumpWidget(createPageView()); + expect( + () => controller.jumpToPage(2), + throwsA( + isAssertionError.having( + (AssertionError error) => error.message, + 'message', + equals('PageController is not attached to a PageView.'), + ), + ), + ); + + // pageController is attached to PageView + await tester.pumpWidget(createPageView(controller)); + expect(() => controller.jumpToPage(2), returnsNormally); + }); + }); + }); + testWidgets( 'Get the page value before the content dimension is determined,do not throw an assertion and return null', (WidgetTester tester) async {