diff --git a/packages/flutter/lib/src/widgets/will_pop_scope.dart b/packages/flutter/lib/src/widgets/will_pop_scope.dart index af5fb1bf06..42515b20fc 100644 --- a/packages/flutter/lib/src/widgets/will_pop_scope.dart +++ b/packages/flutter/lib/src/widgets/will_pop_scope.dart @@ -85,7 +85,6 @@ class _WillPopScopeState extends State { @override void didUpdateWidget(WillPopScope oldWidget) { super.didUpdateWidget(oldWidget); - assert(_route == ModalRoute.of(context)); if (widget.onWillPop != oldWidget.onWillPop && _route != null) { if (oldWidget.onWillPop != null) _route!.removeScopedWillPopCallback(oldWidget.onWillPop!); diff --git a/packages/flutter/test/material/will_pop_test.dart b/packages/flutter/test/material/will_pop_test.dart index 63c60caa50..3fca53ac7f 100644 --- a/packages/flutter/test/material/will_pop_test.dart +++ b/packages/flutter/test/material/will_pop_test.dart @@ -65,13 +65,35 @@ class SampleForm extends StatelessWidget { } // Expose the protected hasScopedWillPopCallback getter -class TestPageRoute extends MaterialPageRoute { - TestPageRoute({ required WidgetBuilder builder }) - : super(builder: builder, maintainState: true); +class _TestPageRoute extends MaterialPageRoute { + _TestPageRoute({ + RouteSettings? settings, + required WidgetBuilder builder, + }) : super(builder: builder, maintainState: true, settings: settings); bool get hasCallback => super.hasScopedWillPopCallback; } +class _TestPage extends Page { + _TestPage({ + required this.builder, + required LocalKey key, + }) : _key = GlobalKey(), + super(key: key); + + final WidgetBuilder builder; + final GlobalKey _key; + + @override + Route createRoute(BuildContext context) { + return _TestPageRoute( + settings: this, + builder: (BuildContext context) { + // keep state during move to another location in tree + return KeyedSubtree(key: _key, child: builder.call(context)); + }); + } +} void main() { testWidgets('ModalRoute scopedWillPopupCallback can inhibit back button', (WidgetTester tester) async { @@ -318,7 +340,7 @@ void main() { late StateSetter contentsSetState; // call this to rebuild the route's SampleForm contents bool contentsEmpty = false; // when true, don't include the SampleForm in the route - final TestPageRoute route = TestPageRoute( + final _TestPageRoute route = _TestPageRoute( builder: (BuildContext context) { return StatefulBuilder( builder: (BuildContext context, StateSetter setState) { @@ -373,4 +395,61 @@ void main() { expect(route.hasCallback, isFalse); }); + + testWidgets('should handle new route if page moved from one navigator to another', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/89133 + late StateSetter contentsSetState; + bool moveToAnotherNavigator = false; + + final List> pages = >[ + _TestPage( + key: UniqueKey(), + builder: (BuildContext context) { + return WillPopScope( + onWillPop: () async => true, + child: const Text('anchor'), + ); + }, + ) + ]; + + Widget _buildNavigator(Key? key, List> pages) { + return Navigator( + key: key, + pages: pages, + onPopPage: (Route route, dynamic result) { + return route.didPop(result); + }, + ); + } + + Widget buildFrame() { + return MaterialApp( + home: Scaffold( + body: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + contentsSetState = setState; + if (moveToAnotherNavigator) { + return _buildNavigator(const ValueKey(1), pages); + } + return _buildNavigator(const ValueKey(2), pages); + }, + ), + ), + ); + } + + await tester.pumpWidget(buildFrame()); + await tester.pump(); + final _TestPageRoute route1 = ModalRoute.of(tester.element(find.text('anchor')))! as _TestPageRoute; + expect(route1.hasCallback, isTrue); + moveToAnotherNavigator = true; + contentsSetState(() {}); + + await tester.pump(); + final _TestPageRoute route2 = ModalRoute.of(tester.element(find.text('anchor')))! as _TestPageRoute; + + expect(route1.hasCallback, isFalse); + expect(route2.hasCallback, isTrue); + }); }