Skip to content

Commit 50eb455

Browse files
committed
[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
1 parent 0413344 commit 50eb455

File tree

4 files changed

+82
-2
lines changed

4 files changed

+82
-2
lines changed

packages/go_router/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
* Updates minimum supported SDK version to Flutter 3.32/Dart 3.8.
44

5+
## 17.0.1
6+
7+
- Fixes an issue where `onEnter` blocking causes navigation stack loss (stale state restoration).
8+
59
## 17.0.0
610

711
- **BREAKING CHANGE**

packages/go_router/lib/src/parser.dart

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class GoRouteInformationParser extends RouteInformationParser<RouteMatchList> {
4444
/// Creates a [GoRouteInformationParser].
4545
GoRouteInformationParser({
4646
required this.configuration,
47-
required GoRouter router,
47+
required this.router,
4848
required this.onParserException,
4949
}) : _routeMatchListCodec = RouteMatchListCodec(configuration),
5050
_onEnterHandler = _OnEnterHandler(
@@ -56,6 +56,9 @@ class GoRouteInformationParser extends RouteInformationParser<RouteMatchList> {
5656
/// The route configuration used for parsing [RouteInformation]s.
5757
final RouteConfiguration configuration;
5858

59+
/// The router instance.
60+
final GoRouter router;
61+
5962
/// Exception handler for parser errors.
6063
final ParserExceptionHandler? onParserException;
6164

@@ -159,6 +162,12 @@ class GoRouteInformationParser extends RouteInformationParser<RouteMatchList> {
159162
},
160163
onCanNotEnter: () {
161164
// If blocked, "stay" on last successful match if available.
165+
if (router.routerDelegate.currentConfiguration.isNotEmpty) {
166+
return SynchronousFuture<RouteMatchList>(
167+
router.routerDelegate.currentConfiguration,
168+
);
169+
}
170+
162171
if (_lastMatchList != null) {
163172
return SynchronousFuture<RouteMatchList>(_lastMatchList!);
164173
}

packages/go_router/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name: go_router
22
description: A declarative router for Flutter based on Navigation 2 supporting
33
deep linking, data-driven routes and more
4-
version: 17.0.0
4+
version: 17.0.1
55
repository: https://github.com/flutter/packages/tree/main/packages/go_router
66
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+go_router%22
77

packages/go_router/test/on_enter_test.dart

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1527,5 +1527,72 @@ void main() {
15271527
containsAllInOrder(<String>['onEnter', 'legacy', 'route-level']),
15281528
);
15291529
});
1530+
testWidgets(
1531+
'onEnter blocking prevents stale state restoration (pop case)',
1532+
(WidgetTester tester) async {
1533+
// This test reproduces https://github.com/flutter/flutter/issues/178853
1534+
// 1. Push A -> B
1535+
// 2. Pop B -> A (simulating system back)
1536+
// 3. Go A -> Blocked
1537+
// 4. onEnter blocks
1538+
// 5. Ensure we stay on A and don't "restore" B (stale state)
1539+
1540+
router = GoRouter(
1541+
initialLocation: '/home',
1542+
onEnter:
1543+
(
1544+
BuildContext context,
1545+
GoRouterState current,
1546+
GoRouterState next,
1547+
GoRouter router,
1548+
) {
1549+
if (next.uri.path == '/blocked') {
1550+
return const Block.stop();
1551+
}
1552+
return const Allow();
1553+
},
1554+
routes: <RouteBase>[
1555+
GoRoute(
1556+
path: '/home',
1557+
builder: (_, __) => const Scaffold(body: Text('Home')),
1558+
),
1559+
GoRoute(
1560+
path: '/allowed',
1561+
builder: (_, __) => const Scaffold(body: Text('Allowed')),
1562+
),
1563+
GoRoute(
1564+
path: '/blocked',
1565+
builder: (_, __) => const Scaffold(body: Text('Blocked')),
1566+
),
1567+
],
1568+
);
1569+
1570+
await tester.pumpWidget(MaterialApp.router(routerConfig: router));
1571+
await tester.pumpAndSettle();
1572+
expect(find.text('Home'), findsOneWidget);
1573+
1574+
// 1. Push allowed
1575+
router.push('/allowed');
1576+
await tester.pumpAndSettle();
1577+
expect(find.text('Allowed'), findsOneWidget);
1578+
1579+
// 2. Pop (simulating system back / imperative pop)
1580+
final NavigatorState navigator = tester.state(find.byType(Navigator).last);
1581+
navigator.pop();
1582+
await tester.pumpAndSettle();
1583+
expect(find.text('Home'), findsOneWidget);
1584+
1585+
// 3. Attempt blocked navigation
1586+
router.go('/blocked');
1587+
await tester.pumpAndSettle();
1588+
1589+
// 4. Verify blocking worked
1590+
expect(find.text('Blocked'), findsNothing);
1591+
1592+
// 5. Verify we didn't restore the popped route (Allowed)
1593+
expect(find.text('Allowed'), findsNothing);
1594+
expect(find.text('Home'), findsOneWidget);
1595+
},
1596+
);
15301597
});
15311598
}

0 commit comments

Comments
 (0)