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..2e1c0f099b1 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; @@ -158,7 +161,13 @@ 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, + ); + } + 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..d322b870a5a 100644 --- a/packages/go_router/test/on_enter_test.dart +++ b/packages/go_router/test/on_enter_test.dart @@ -1527,5 +1527,62 @@ 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: (_, __, next, ___) => + next.uri.path == '/blocked' ? const Block.stop() : 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); + }, + ); }); }