diff --git a/packages/go_router/CHANGELOG.md b/packages/go_router/CHANGELOG.md index f1d95f90c9fc..9fa25e3824c9 100644 --- a/packages/go_router/CHANGELOG.md +++ b/packages/go_router/CHANGELOG.md @@ -1,3 +1,7 @@ +## 17.1.1 + +- Fixes `pop()` restoring stale configuration when route has `onExit`, which could cause the popped route to reappear with async redirects. + ## 17.1.0 - Adds `TypedQueryParameter` annotation to override parameter names in `TypedGoRoute` constructors. diff --git a/packages/go_router/lib/src/router.dart b/packages/go_router/lib/src/router.dart index 7e885742ec52..de8bdf1b8887 100644 --- a/packages/go_router/lib/src/router.dart +++ b/packages/go_router/lib/src/router.dart @@ -583,8 +583,13 @@ class GoRouter implements RouterConfig { log('popping ${routerDelegate.currentConfiguration.uri}'); return true; }()); + final RouteMatchList configBeforePop = routerDelegate.currentConfiguration; routerDelegate.pop(result); - restore(routerDelegate.currentConfiguration); + // Only restore when the pop completed synchronously (no onExit). + // If deferred, currentConfiguration is still the same instance. + if (!identical(routerDelegate.currentConfiguration, configBeforePop)) { + restore(routerDelegate.currentConfiguration); + } } /// Refresh the route. diff --git a/packages/go_router/pubspec.yaml b/packages/go_router/pubspec.yaml index 55ad56b1058c..91402a841cb7 100644 --- a/packages/go_router/pubspec.yaml +++ b/packages/go_router/pubspec.yaml @@ -1,7 +1,7 @@ name: go_router description: A declarative router for Flutter based on Navigation 2 supporting deep linking, data-driven routes and more -version: 17.1.0 +version: 17.1.1 repository: https://github.com/flutter/packages/tree/main/packages/go_router issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+go_router%22 diff --git a/packages/go_router/test/on_exit_test.dart b/packages/go_router/test/on_exit_test.dart index 2b1d0873cb95..56f5c6119698 100644 --- a/packages/go_router/test/on_exit_test.dart +++ b/packages/go_router/test/on_exit_test.dart @@ -493,4 +493,89 @@ void main() { expect(onExitState2.fullPath, '/route-2/:id2'); }, ); + + // Regression test: pop() with onExit + async redirect must not restore + // stale configuration. + testWidgets( + 'pop does not call restore with stale config when route has onExit', + (WidgetTester tester) async { + final homeKey = UniqueKey(); + final detailKey = UniqueKey(); + + final GoRouter router = await createRouter( + [ + GoRoute( + path: '/', + builder: (_, __) => DummyScreen(key: homeKey), + routes: [ + GoRoute( + path: 'detail', + onExit: (_, __) => true, + builder: (_, __) => DummyScreen(key: detailKey), + ), + ], + ), + ], + tester, + initialLocation: '/detail', + redirect: (_, GoRouterState state) async { + // Async redirect — completes in a later microtask. + await Future.delayed(Duration.zero); + return null; + }, + ); + + await tester.pumpAndSettle(); + expect(find.byKey(detailKey), findsOneWidget); + + router.pop(); + await tester.pumpAndSettle(); + + // The detail route should be gone after pop. + expect( + find.byKey(detailKey), + findsNothing, + reason: + 'Route with onExit should be properly popped ' + 'even when async redirect is present', + ); + expect(find.byKey(homeKey), findsOneWidget); + }, + ); + + // Verify that pop is correctly cancelled when onExit returns false. + testWidgets('pop is cancelled when onExit returns false', ( + WidgetTester tester, + ) async { + final homeKey = UniqueKey(); + final detailKey = UniqueKey(); + + final GoRouter router = await createRouter( + [ + GoRoute( + path: '/', + builder: (_, __) => DummyScreen(key: homeKey), + routes: [ + GoRoute( + path: 'detail', + onExit: (_, __) => false, // Always prevent leaving. + builder: (_, __) => DummyScreen(key: detailKey), + ), + ], + ), + ], + tester, + initialLocation: '/detail', + ); + + await tester.pumpAndSettle(); + expect(find.byKey(detailKey), findsOneWidget); + + router.pop(); + await tester.pumpAndSettle(); + + // Should still be on the detail page. + expect(find.byKey(detailKey), findsOneWidget); + expect(find.byKey(homeKey), findsNothing); + }); }