From 50eb455337726990de60708ba9f12d7c41c7e8cd Mon Sep 17 00:00:00 2001 From: Arin Faraj Date: Thu, 20 Nov 2025 11:46:35 +0300 Subject: [PATCH 1/3] [go_router] Fix stale state restoration in onEnter and bump version to 17.0.1 This commit fixes an issue where `onEnter` blocking caused navigation stack loss due to stale state restoration. It ensures that `currentConfiguration` is used instead of the internal cache when navigation is blocked. - Bumps version to 17.0.1 - Updates CHANGELOG.md - Adds regression test in on_enter_test.dart Fixes flutter/flutter#178853 --- packages/go_router/CHANGELOG.md | 4 ++ packages/go_router/lib/src/parser.dart | 11 +++- packages/go_router/pubspec.yaml | 2 +- packages/go_router/test/on_enter_test.dart | 67 ++++++++++++++++++++++ 4 files changed, 82 insertions(+), 2 deletions(-) diff --git a/packages/go_router/CHANGELOG.md b/packages/go_router/CHANGELOG.md index cd0790837d6..fbac6c1b75e 100644 --- a/packages/go_router/CHANGELOG.md +++ b/packages/go_router/CHANGELOG.md @@ -2,6 +2,10 @@ * Updates minimum supported SDK version to Flutter 3.32/Dart 3.8. +## 17.0.1 + +- Fixes an issue where `onEnter` blocking causes navigation stack loss (stale state restoration). + ## 17.0.0 - **BREAKING CHANGE** diff --git a/packages/go_router/lib/src/parser.dart b/packages/go_router/lib/src/parser.dart index 288682d1603..cd31d08c874 100644 --- a/packages/go_router/lib/src/parser.dart +++ b/packages/go_router/lib/src/parser.dart @@ -44,7 +44,7 @@ class GoRouteInformationParser extends RouteInformationParser { /// Creates a [GoRouteInformationParser]. GoRouteInformationParser({ required this.configuration, - required GoRouter router, + required this.router, required this.onParserException, }) : _routeMatchListCodec = RouteMatchListCodec(configuration), _onEnterHandler = _OnEnterHandler( @@ -56,6 +56,9 @@ class GoRouteInformationParser extends RouteInformationParser { /// The route configuration used for parsing [RouteInformation]s. final RouteConfiguration configuration; + /// The router instance. + final GoRouter router; + /// Exception handler for parser errors. final ParserExceptionHandler? onParserException; @@ -159,6 +162,12 @@ class GoRouteInformationParser extends RouteInformationParser { }, onCanNotEnter: () { // If blocked, "stay" on last successful match if available. + if (router.routerDelegate.currentConfiguration.isNotEmpty) { + return SynchronousFuture( + router.routerDelegate.currentConfiguration, + ); + } + if (_lastMatchList != null) { return SynchronousFuture(_lastMatchList!); } diff --git a/packages/go_router/pubspec.yaml b/packages/go_router/pubspec.yaml index cf88637eef5..1fe56ab74d0 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.0.0 +version: 17.0.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_enter_test.dart b/packages/go_router/test/on_enter_test.dart index 310780e9715..b34418ae6e3 100644 --- a/packages/go_router/test/on_enter_test.dart +++ b/packages/go_router/test/on_enter_test.dart @@ -1527,5 +1527,72 @@ void main() { containsAllInOrder(['onEnter', 'legacy', 'route-level']), ); }); + testWidgets( + 'onEnter blocking prevents stale state restoration (pop case)', + (WidgetTester tester) async { + // This test reproduces https://github.com/flutter/flutter/issues/178853 + // 1. Push A -> B + // 2. Pop B -> A (simulating system back) + // 3. Go A -> Blocked + // 4. onEnter blocks + // 5. Ensure we stay on A and don't "restore" B (stale state) + + router = GoRouter( + initialLocation: '/home', + onEnter: + ( + BuildContext context, + GoRouterState current, + GoRouterState next, + GoRouter router, + ) { + if (next.uri.path == '/blocked') { + return const Block.stop(); + } + return const Allow(); + }, + routes: [ + GoRoute( + path: '/home', + builder: (_, __) => const Scaffold(body: Text('Home')), + ), + GoRoute( + path: '/allowed', + builder: (_, __) => const Scaffold(body: Text('Allowed')), + ), + GoRoute( + path: '/blocked', + builder: (_, __) => const Scaffold(body: Text('Blocked')), + ), + ], + ); + + await tester.pumpWidget(MaterialApp.router(routerConfig: router)); + await tester.pumpAndSettle(); + expect(find.text('Home'), findsOneWidget); + + // 1. Push allowed + router.push('/allowed'); + await tester.pumpAndSettle(); + expect(find.text('Allowed'), findsOneWidget); + + // 2. Pop (simulating system back / imperative pop) + final NavigatorState navigator = tester.state(find.byType(Navigator).last); + navigator.pop(); + await tester.pumpAndSettle(); + expect(find.text('Home'), findsOneWidget); + + // 3. Attempt blocked navigation + router.go('/blocked'); + await tester.pumpAndSettle(); + + // 4. Verify blocking worked + expect(find.text('Blocked'), findsNothing); + + // 5. Verify we didn't restore the popped route (Allowed) + expect(find.text('Allowed'), findsNothing); + expect(find.text('Home'), findsOneWidget); + }, + ); }); } From 52fbbb61649254f38667d2514c44c63f08eb7f85 Mon Sep 17 00:00:00 2001 From: Arin Abdulmajeed Date: Thu, 20 Nov 2025 12:13:12 +0300 Subject: [PATCH 2/3] Update packages/go_router/lib/src/parser.dart Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- packages/go_router/lib/src/parser.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/go_router/lib/src/parser.dart b/packages/go_router/lib/src/parser.dart index cd31d08c874..2e1c0f099b1 100644 --- a/packages/go_router/lib/src/parser.dart +++ b/packages/go_router/lib/src/parser.dart @@ -161,7 +161,7 @@ class GoRouteInformationParser extends RouteInformationParser { }); }, onCanNotEnter: () { - // If blocked, "stay" on last successful match if available. + // If blocked, stay on the current route by restoring the last known good configuration. if (router.routerDelegate.currentConfiguration.isNotEmpty) { return SynchronousFuture( router.routerDelegate.currentConfiguration, From a38fbe1702e1c3f74e0b12de36a5865b745aa655 Mon Sep 17 00:00:00 2001 From: Arin Abdulmajeed Date: Thu, 20 Nov 2025 12:13:19 +0300 Subject: [PATCH 3/3] Update packages/go_router/test/on_enter_test.dart Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- packages/go_router/test/on_enter_test.dart | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/packages/go_router/test/on_enter_test.dart b/packages/go_router/test/on_enter_test.dart index b34418ae6e3..d322b870a5a 100644 --- a/packages/go_router/test/on_enter_test.dart +++ b/packages/go_router/test/on_enter_test.dart @@ -1539,18 +1539,8 @@ void main() { router = GoRouter( initialLocation: '/home', - onEnter: - ( - BuildContext context, - GoRouterState current, - GoRouterState next, - GoRouter router, - ) { - if (next.uri.path == '/blocked') { - return const Block.stop(); - } - return const Allow(); - }, + onEnter: (_, __, next, ___) => + next.uri.path == '/blocked' ? const Block.stop() : const Allow(), routes: [ GoRoute( path: '/home',