diff --git a/packages/go_router/CHANGELOG.md b/packages/go_router/CHANGELOG.md index 7134ea8aa4c9..0e90fec1fa2e 100644 --- a/packages/go_router/CHANGELOG.md +++ b/packages/go_router/CHANGELOG.md @@ -1,3 +1,7 @@ +## 14.7.0 + +- Adds fragment support to GoRouter, enabling direct specification and automatic handling of fragments in routes. + ## 14.6.4 - Rephrases readme. @@ -7,6 +11,7 @@ - Updates minimum supported SDK version to Flutter 3.22/Dart 3.4. - Updates readme. + ## 14.6.2 - Replaces deprecated collection method usage. @@ -23,7 +28,6 @@ - Adds preload support to StatefulShellRoute, configurable via `preload` parameter on StatefulShellBranch. - ## 14.4.1 - Adds `missing_code_block_language_in_doc_comment` lint. @@ -42,7 +46,7 @@ ## 14.2.8 -- Updated custom_stateful_shell_route example to better support swiping in TabView as well as demonstration of the use of PageView. +- Updated custom_stateful_shell_route example to better support swiping in TabView as well as demonstration of the use of PageView. ## 14.2.7 @@ -1146,3 +1150,4 @@ ## 0.1.0 - squatting on the package name (I'm not too proud to admit it) + diff --git a/packages/go_router/lib/src/configuration.dart b/packages/go_router/lib/src/configuration.dart index cc671066218d..95fd10029133 100644 --- a/packages/go_router/lib/src/configuration.dart +++ b/packages/go_router/lib/src/configuration.dart @@ -253,12 +253,14 @@ class RouteConfiguration { String name, { Map pathParameters = const {}, Map queryParameters = const {}, + String? fragment, }) { assert(() { log('getting location for name: ' '"$name"' '${pathParameters.isEmpty ? '' : ', pathParameters: $pathParameters'}' - '${queryParameters.isEmpty ? '' : ', queryParameters: $queryParameters'}'); + '${queryParameters.isEmpty ? '' : ', queryParameters: $queryParameters'}' + '${fragment != null ? ', fragment: $fragment' : ''}'); return true; }()); assert(_nameToPath.containsKey(name), 'unknown route name: $name'); @@ -285,7 +287,8 @@ class RouteConfiguration { final String location = patternToPath(path, encodedParams); return Uri( path: location, - queryParameters: queryParameters.isEmpty ? null : queryParameters) + queryParameters: queryParameters.isEmpty ? null : queryParameters, + fragment: fragment) .toString(); } diff --git a/packages/go_router/lib/src/misc/extensions.dart b/packages/go_router/lib/src/misc/extensions.dart index c137022b8020..3c390c945988 100644 --- a/packages/go_router/lib/src/misc/extensions.dart +++ b/packages/go_router/lib/src/misc/extensions.dart @@ -16,9 +16,12 @@ extension GoRouterHelper on BuildContext { String name, { Map pathParameters = const {}, Map queryParameters = const {}, + String? fragment, }) => GoRouter.of(this).namedLocation(name, - pathParameters: pathParameters, queryParameters: queryParameters); + pathParameters: pathParameters, + queryParameters: queryParameters, + fragment: fragment); /// Navigate to a location. void go(String location, {Object? extra}) => @@ -30,13 +33,13 @@ extension GoRouterHelper on BuildContext { Map pathParameters = const {}, Map queryParameters = const {}, Object? extra, + String? fragment, }) => - GoRouter.of(this).goNamed( - name, - pathParameters: pathParameters, - queryParameters: queryParameters, - extra: extra, - ); + GoRouter.of(this).goNamed(name, + pathParameters: pathParameters, + queryParameters: queryParameters, + extra: extra, + fragment: fragment); /// Push a location onto the page stack. /// diff --git a/packages/go_router/lib/src/router.dart b/packages/go_router/lib/src/router.dart index 11f40f505cac..1070c42b047e 100644 --- a/packages/go_router/lib/src/router.dart +++ b/packages/go_router/lib/src/router.dart @@ -331,15 +331,15 @@ class GoRouter implements RouterConfig { /// Get a location from route name and parameters. /// This is useful for redirecting to a named location. - String namedLocation( - String name, { - Map pathParameters = const {}, - Map queryParameters = const {}, - }) => + String namedLocation(String name, + {Map pathParameters = const {}, + Map queryParameters = const {}, + String? fragment}) => configuration.namedLocation( name, pathParameters: pathParameters, queryParameters: queryParameters, + fragment: fragment, ); /// Navigate to a URI location w/ optional query parameters, e.g. @@ -366,10 +366,15 @@ class GoRouter implements RouterConfig { Map pathParameters = const {}, Map queryParameters = const {}, Object? extra, + String? fragment, }) => + + /// Construct location with optional fragment, using null-safe navigation go( namedLocation(name, - pathParameters: pathParameters, queryParameters: queryParameters), + pathParameters: pathParameters, + queryParameters: queryParameters, + fragment: fragment), extra: extra, ); diff --git a/packages/go_router/lib/src/state.dart b/packages/go_router/lib/src/state.dart index 0a1fb54ba79b..147d191140a8 100644 --- a/packages/go_router/lib/src/state.dart +++ b/packages/go_router/lib/src/state.dart @@ -157,9 +157,14 @@ class GoRouterState { String name, { Map pathParameters = const {}, Map queryParameters = const {}, + String? fragment, }) { + // Generate base location using configuration, with optional path and query parameters + // Then conditionally append fragment if it exists and is not empty return _configuration.namedLocation(name, - pathParameters: pathParameters, queryParameters: queryParameters); + pathParameters: pathParameters, + queryParameters: queryParameters, + fragment: fragment); } @override diff --git a/packages/go_router/pubspec.yaml b/packages/go_router/pubspec.yaml index ef2d9422b465..5370fc7cac36 100644 --- a/packages/go_router/pubspec.yaml +++ b/packages/go_router/pubspec.yaml @@ -1,7 +1,8 @@ name: go_router description: A declarative router for Flutter based on Navigation 2 supporting deep linking, data-driven routes and more -version: 14.6.4 +version: 14.7.0 + 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/go_route_test.dart b/packages/go_router/test/go_route_test.dart index 0ff437c0c1c7..752a1a16580f 100644 --- a/packages/go_router/test/go_route_test.dart +++ b/packages/go_router/test/go_route_test.dart @@ -249,6 +249,58 @@ void main() { expect(tester.takeException(), isAssertionError); }); + testWidgets('redirects to a valid route based on fragment.', + (WidgetTester tester) async { + final GoRouter router = await createRouter( + [ + GoRoute( + path: '/', + builder: (_, __) => const Text('home'), + routes: [ + GoRoute( + path: 'route', + name: 'route', + redirect: (BuildContext context, GoRouterState state) { + // Redirection logic based on the fragment in the URI + if (state.uri.fragment == '1') { + // If fragment is "1", redirect to "/route/1" + return '/route/1'; + } + return null; // No redirection for other cases + }, + routes: [ + GoRoute( + path: '1', + builder: (_, __) => + const Text('/route/1'), // Renders "/route/1" text + ), + ], + ), + ], + ), + ], + tester, + ); + // Verify that the root route ("/") initially displays the "home" text + expect(find.text('home'), findsOneWidget); + + // Generate a location string for the named route "route" with fragment "2" + final String locationWithFragment = + router.namedLocation('route', fragment: '2'); + expect(locationWithFragment, + '/route#2'); // Expect the generated location to be "/route#2" + + // Navigate to the named route "route" with fragment "1" + router.goNamed('route', fragment: '1'); + await tester.pumpAndSettle(); + + // Verify that navigating to "/route" with fragment "1" redirects to "/route/1" + expect(find.text('/route/1'), findsOneWidget); + + // Ensure no exceptions occurred during navigation + expect(tester.takeException(), isNull); + }); + testWidgets('throw if sub route does not conform with parent navigator key', (WidgetTester tester) async { final GlobalKey key1 = GlobalKey(); diff --git a/packages/go_router/test/test_helpers.dart b/packages/go_router/test/test_helpers.dart index e6f69c507885..cb1c2acdb966 100644 --- a/packages/go_router/test/test_helpers.dart +++ b/packages/go_router/test/test_helpers.dart @@ -45,16 +45,17 @@ class GoRouterNamedLocationSpy extends GoRouter { String? name; Map? pathParameters; Map? queryParameters; + String? fragment; @override - String namedLocation( - String name, { - Map pathParameters = const {}, - Map queryParameters = const {}, - }) { + String namedLocation(String name, + {Map pathParameters = const {}, + Map queryParameters = const {}, + String? fragment}) { this.name = name; this.pathParameters = pathParameters; this.queryParameters = queryParameters; + this.fragment = fragment; return ''; } } @@ -85,6 +86,7 @@ class GoRouterGoNamedSpy extends GoRouter { Map? pathParameters; Map? queryParameters; Object? extra; + String? fragment; @override void goNamed( @@ -92,11 +94,13 @@ class GoRouterGoNamedSpy extends GoRouter { Map pathParameters = const {}, Map queryParameters = const {}, Object? extra, + String? fragment, }) { this.name = name; this.pathParameters = pathParameters; this.queryParameters = queryParameters; this.extra = extra; + this.fragment = fragment; } }