diff --git a/packages/flutter/lib/src/widgets/navigator.dart b/packages/flutter/lib/src/widgets/navigator.dart index 5481641d12b..cf5e694de40 100644 --- a/packages/flutter/lib/src/widgets/navigator.dart +++ b/packages/flutter/lib/src/widgets/navigator.dart @@ -985,11 +985,15 @@ class DefaultTransitionDelegate extends TransitionDelegate { if (hasPagelessRoute) { final List pagelessRoutes = pageRouteToPagelessRoutes[exitingPageRoute]!; for (final RouteTransitionRecord pagelessRoute in pagelessRoutes) { - assert(pagelessRoute.isWaitingForExitingDecision); - if (isLastExitingPageRoute && pagelessRoute == pagelessRoutes.last) { - pagelessRoute.markForPop(pagelessRoute.route.currentResult); - } else { - pagelessRoute.markForComplete(pagelessRoute.route.currentResult); + // It is possible that a pageless route that belongs to an exiting + // page-based route does not require exiting decision. This can + // happen if the page list is updated right after a Navigator.pop. + if (pagelessRoute.isWaitingForExitingDecision) { + if (isLastExitingPageRoute && pagelessRoute == pagelessRoutes.last) { + pagelessRoute.markForPop(pagelessRoute.route.currentResult); + } else { + pagelessRoute.markForComplete(pagelessRoute.route.currentResult); + } } } } @@ -3655,7 +3659,7 @@ class NavigatorState extends State with TickerProviderStateMixin, Res () => <_RouteEntry>[], ); pagelessRoutes.add(potentialEntryToRemove); - if (previousOldPageRouteEntry!.isWaitingForExitingDecision) + if (previousOldPageRouteEntry!.isWaitingForExitingDecision && potentialEntryToRemove.isPresent) potentialEntryToRemove.markNeedsExitingDecision(); continue; } diff --git a/packages/flutter/test/widgets/navigator_test.dart b/packages/flutter/test/widgets/navigator_test.dart index a08d14bf201..08d4ab5b941 100644 --- a/packages/flutter/test/widgets/navigator_test.dart +++ b/packages/flutter/test/widgets/navigator_test.dart @@ -3195,6 +3195,43 @@ void main() { expect(find.text('initial'), findsOneWidget); }); + testWidgets('can update pages before a pageless route has finished popping', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/68162. + final GlobalKey navigator = GlobalKey(); + List> myPages = [ + const TestPage(key: ValueKey('1'), name: 'initial'), + const TestPage(key: ValueKey('2'), name: 'second'), + ]; + bool onPopPage(Route route, dynamic result) { + myPages.removeWhere((Page page) => route.settings == page); + return route.didPop(result); + } + await tester.pumpWidget( + buildNavigator(pages: myPages, onPopPage: onPopPage, key: navigator) + ); + // Pushes a pageless route. + showDialog( + useRootNavigator: false, + context: navigator.currentContext!, + builder: (BuildContext context) => const Text('dialog') + ); + await tester.pumpAndSettle(); + expect(find.text('dialog'), findsOneWidget); + // Pops the pageless route. + navigator.currentState!.pop(); + // Before the pop finishes, updates the page list. + myPages = [ + const TestPage(key: ValueKey('1'), name: 'initial'), + ]; + await tester.pumpWidget( + buildNavigator(pages: myPages, onPopPage: onPopPage, key: navigator) + ); + // It should not crash the app. + expect(tester.takeException(), isNull); + await tester.pumpAndSettle(); + expect(find.text('initial'), findsOneWidget); + }); + testWidgets('pages remove and add trigger observer in the right order', (WidgetTester tester) async { final GlobalKey navigator = GlobalKey(); List myPages = [