From 522190622578b242eeed7f4aa4a3a52561c6a536 Mon Sep 17 00:00:00 2001 From: zhanglangning Date: Wed, 12 Aug 2026 06:54:57 +0800 Subject: [PATCH] [go_router] Fix relative navigation after imperative navigation --- packages/go_router/lib/src/router.dart | 22 ++++++-- .../change_2026_08_12_1786487000886.yaml | 3 ++ .../go_router/test/imperative_api_test.dart | 53 +++++++++++++++++++ 3 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 packages/go_router/pending_changelogs/change_2026_08_12_1786487000886.yaml diff --git a/packages/go_router/lib/src/router.dart b/packages/go_router/lib/src/router.dart index 5f1ff5dbb793..feb4a0a835e8 100644 --- a/packages/go_router/lib/src/router.dart +++ b/packages/go_router/lib/src/router.dart @@ -17,6 +17,7 @@ import 'misc/constants.dart'; import 'misc/inherited_router.dart'; import 'on_enter.dart'; import 'parser.dart'; +import 'path_utils.dart'; import 'route.dart'; import 'state.dart'; @@ -402,11 +403,24 @@ class GoRouter implements RouterConfig { fragment: fragment, ); + String _resolveRelativeLocation(String location) { + if (!location.startsWith('./')) { + return location; + } + + final RouteMatch? lastMatch = routerDelegate.currentConfiguration.lastOrNull; + if (lastMatch is! ImperativeRouteMatch) { + return location; + } + + return concatenateUris(lastMatch.matches.uri, Uri.parse(location)).toString(); + } + /// Navigate to a URI location w/ optional query parameters, e.g. /// `/family/f2/person/p1?color=blue` void go(String location, {Object? extra}) { log('going to $location'); - routeInformationProvider.go(location, extra: extra); + routeInformationProvider.go(_resolveRelativeLocation(location), extra: extra); } /// Restore the RouteMatchList @@ -448,7 +462,7 @@ class GoRouter implements RouterConfig { Future push(String location, {Object? extra}) async { log('pushing $location'); return routeInformationProvider.push( - location, + _resolveRelativeLocation(location), base: routerDelegate.currentConfiguration, extra: extra, ); @@ -478,7 +492,7 @@ class GoRouter implements RouterConfig { Future pushReplacement(String location, {Object? extra}) { log('pushReplacement $location'); return routeInformationProvider.pushReplacement( - location, + _resolveRelativeLocation(location), base: routerDelegate.currentConfiguration, extra: extra, ); @@ -516,7 +530,7 @@ class GoRouter implements RouterConfig { Future replace(String location, {Object? extra}) { log('replace $location'); return routeInformationProvider.replace( - location, + _resolveRelativeLocation(location), base: routerDelegate.currentConfiguration, extra: extra, ); diff --git a/packages/go_router/pending_changelogs/change_2026_08_12_1786487000886.yaml b/packages/go_router/pending_changelogs/change_2026_08_12_1786487000886.yaml new file mode 100644 index 000000000000..b40e45d81de5 --- /dev/null +++ b/packages/go_router/pending_changelogs/change_2026_08_12_1786487000886.yaml @@ -0,0 +1,3 @@ +changelog: | + - Fixes relative navigation resolving against the underlying route instead of the imperatively-pushed route. +version: patch diff --git a/packages/go_router/test/imperative_api_test.dart b/packages/go_router/test/imperative_api_test.dart index a00b7c20bf78..ebd4d5a1b510 100644 --- a/packages/go_router/test/imperative_api_test.dart +++ b/packages/go_router/test/imperative_api_test.dart @@ -286,4 +286,57 @@ void main() { expect(find.text('shell'), findsNothing); expect(find.byKey(e), findsOneWidget); }); + + for (final MapEntry operation + in { + 'go': (GoRouter router, String location) => router.go(location), + 'push': (GoRouter router, String location) => router.push(location), + 'pushReplacement': (GoRouter router, String location) => + router.pushReplacement(location), + 'replace': (GoRouter router, String location) => router.replace(location), + }.entries) { + testWidgets('${operation.key} resolves a relative route against the imperative top', ( + WidgetTester tester, + ) async { + // Regression test for https://github.com/flutter/flutter/issues/182441. + final homeReviews = UniqueKey(); + final productReviews = UniqueKey(); + final routes = [ + GoRoute( + path: '/home', + builder: (_, _) => const DummyScreen(), + routes: [ + GoRoute( + path: 'reviews', + builder: (_, _) => DummyScreen(key: homeReviews), + ), + ], + ), + GoRoute( + path: '/products/:id', + builder: (_, _) => const DummyScreen(), + routes: [ + GoRoute( + path: 'reviews', + builder: (_, _) => DummyScreen(key: productReviews), + ), + ], + ), + ]; + final GoRouter router = await createRouter(routes, tester, initialLocation: '/home'); + + router.push('/products/9'); + await tester.pumpAndSettle(); + expect(router.state.uri.path, '/products/9'); + + operation.value(router, './reviews?sort=recent#top'); + await tester.pumpAndSettle(); + + expect(router.state.uri.path, '/products/9/reviews'); + expect(router.state.uri.queryParameters, {'sort': 'recent'}); + expect(router.state.uri.fragment, 'top'); + expect(find.byKey(productReviews), findsOneWidget); + expect(find.byKey(homeReviews), findsNothing); + }); + } }