Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/go_router/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**
Expand Down
11 changes: 10 additions & 1 deletion packages/go_router/lib/src/parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class GoRouteInformationParser extends RouteInformationParser<RouteMatchList> {
/// Creates a [GoRouteInformationParser].
GoRouteInformationParser({
required this.configuration,
required GoRouter router,
required this.router,
required this.onParserException,
}) : _routeMatchListCodec = RouteMatchListCodec(configuration),
_onEnterHandler = _OnEnterHandler(
Expand All @@ -56,6 +56,9 @@ class GoRouteInformationParser extends RouteInformationParser<RouteMatchList> {
/// 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;

Expand Down Expand Up @@ -159,6 +162,12 @@ class GoRouteInformationParser extends RouteInformationParser<RouteMatchList> {
},
onCanNotEnter: () {
// If blocked, "stay" on last successful match if available.
if (router.routerDelegate.currentConfiguration.isNotEmpty) {
return SynchronousFuture<RouteMatchList>(
router.routerDelegate.currentConfiguration,
);
}

if (_lastMatchList != null) {
return SynchronousFuture<RouteMatchList>(_lastMatchList!);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/go_router/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -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

Expand Down
67 changes: 67 additions & 0 deletions packages/go_router/test/on_enter_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1527,5 +1527,72 @@ void main() {
containsAllInOrder(<String>['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: <RouteBase>[
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);
},
);
});
}