Skip to content

Commit

Permalink
[go _route] fragment parameter added (#8232)
Browse files Browse the repository at this point in the history
### Description of Change

This PR addresses the need for fragments/hashes to be treated as
first-party parameters within the `go_router` package, as highlighted in
[issue #150155](flutter/flutter#150155).
Previously, users had to manually append the fragment to the URL, which
could lead to potential bugs. With this update, the `fragment` is now a
dedicated parameter, allowing for a more seamless and bug-free
integration.

#### Before:

```dart
final location = context.namedLocation('some_route'); // const nested records
// Manually adding the fragment, hoping there aren't any weird bugs surrounding it
context.replace('$location#https://a.url/that?i=mightuse');
```

#### After:

```dart
// Directly passing the fragment as a parameter
context.goNamed("details", fragment: 'https://a.url/that?i=mightuse');

// or

final location = GoRouterState.of(context).namedLocation(
  'details',
  fragment: 'section3',
);
context.go(location);

// or 

final location = context.namedLocation(
 'details',
 fragment: 'section3',
);
context.go(location);
```

### Issues Fixed

This PR resolves [issue
#150155](flutter/flutter#150155).

---

## Pre-launch Checklist

- [✔️ ] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [✔️ ] I read the [Tree Hygiene] page, which explains my
responsibilities.
- [✔️ ] I read and followed the [relevant style guides] and ran the
auto-formatter. (Unlike the flutter/flutter repo, the flutter/packages
repo does use `dart format`.)
- [x] I signed the [CLA].
- [✔️ ] The title of the PR starts with the name of the package
surrounded by square brackets, e.g. `[shared_preferences]`
- [✔️ ] I [linked to at least one issue that this PR fixes] in the
description above.
- [✔️ ] I updated `pubspec.yaml` with an appropriate new version
according to the [pub versioning philosophy], or this PR is [exempt from
version changes].
- [ ✔️] I updated `CHANGELOG.md` to add a description of the change,
[following repository CHANGELOG style], or this PR is [exempt from
CHANGELOG changes].
- [✔️ ] I updated/added relevant documentation (doc comments with
`///`).
- [ ✔️] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [ ✔️] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/packages/blob/main/CONTRIBUTING.md
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md
[relevant style guides]:
https://github.com/flutter/packages/blob/main/CONTRIBUTING.md#style
[CLA]: https://cla.developers.google.com/
[Discord]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Chat.md
[linked to at least one issue that this PR fixes]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#overview
[pub versioning philosophy]: https://dart.dev/tools/pub/versioning
[exempt from version changes]:
https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#version
[following repository CHANGELOG style]:
https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changelog-style
[exempt from CHANGELOG changes]:
https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changelog
[test-exempt]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#tests
  • Loading branch information
AffanShaikhsurab authored Jan 23, 2025
1 parent 06abd68 commit ac08525
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 24 deletions.
9 changes: 7 additions & 2 deletions packages/go_router/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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

Expand Down Expand Up @@ -1146,3 +1150,4 @@
## 0.1.0

- squatting on the package name (I'm not too proud to admit it)

7 changes: 5 additions & 2 deletions packages/go_router/lib/src/configuration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -253,12 +253,14 @@ class RouteConfiguration {
String name, {
Map<String, String> pathParameters = const <String, String>{},
Map<String, dynamic> queryParameters = const <String, dynamic>{},
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');
Expand All @@ -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();
}

Expand Down
17 changes: 10 additions & 7 deletions packages/go_router/lib/src/misc/extensions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@ extension GoRouterHelper on BuildContext {
String name, {
Map<String, String> pathParameters = const <String, String>{},
Map<String, dynamic> queryParameters = const <String, dynamic>{},
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}) =>
Expand All @@ -30,13 +33,13 @@ extension GoRouterHelper on BuildContext {
Map<String, String> pathParameters = const <String, String>{},
Map<String, dynamic> queryParameters = const <String, dynamic>{},
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.
///
Expand Down
17 changes: 11 additions & 6 deletions packages/go_router/lib/src/router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -331,15 +331,15 @@ class GoRouter implements RouterConfig<RouteMatchList> {

/// Get a location from route name and parameters.
/// This is useful for redirecting to a named location.
String namedLocation(
String name, {
Map<String, String> pathParameters = const <String, String>{},
Map<String, dynamic> queryParameters = const <String, dynamic>{},
}) =>
String namedLocation(String name,
{Map<String, String> pathParameters = const <String, String>{},
Map<String, dynamic> queryParameters = const <String, dynamic>{},
String? fragment}) =>
configuration.namedLocation(
name,
pathParameters: pathParameters,
queryParameters: queryParameters,
fragment: fragment,
);

/// Navigate to a URI location w/ optional query parameters, e.g.
Expand All @@ -366,10 +366,15 @@ class GoRouter implements RouterConfig<RouteMatchList> {
Map<String, String> pathParameters = const <String, String>{},
Map<String, dynamic> queryParameters = const <String, dynamic>{},
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,
);

Expand Down
7 changes: 6 additions & 1 deletion packages/go_router/lib/src/state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,14 @@ class GoRouterState {
String name, {
Map<String, String> pathParameters = const <String, String>{},
Map<String, String> queryParameters = const <String, String>{},
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
Expand Down
3 changes: 2 additions & 1 deletion packages/go_router/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -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

Expand Down
52 changes: 52 additions & 0 deletions packages/go_router/test/go_route_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<RouteBase>[
GoRoute(
path: '/',
builder: (_, __) => const Text('home'),
routes: <RouteBase>[
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: <RouteBase>[
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<NavigatorState> key1 = GlobalKey<NavigatorState>();
Expand Down
14 changes: 9 additions & 5 deletions packages/go_router/test/test_helpers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,17 @@ class GoRouterNamedLocationSpy extends GoRouter {
String? name;
Map<String, String>? pathParameters;
Map<String, dynamic>? queryParameters;
String? fragment;

@override
String namedLocation(
String name, {
Map<String, String> pathParameters = const <String, String>{},
Map<String, dynamic> queryParameters = const <String, dynamic>{},
}) {
String namedLocation(String name,
{Map<String, String> pathParameters = const <String, String>{},
Map<String, dynamic> queryParameters = const <String, dynamic>{},
String? fragment}) {
this.name = name;
this.pathParameters = pathParameters;
this.queryParameters = queryParameters;
this.fragment = fragment;
return '';
}
}
Expand Down Expand Up @@ -85,18 +86,21 @@ class GoRouterGoNamedSpy extends GoRouter {
Map<String, String>? pathParameters;
Map<String, dynamic>? queryParameters;
Object? extra;
String? fragment;

@override
void goNamed(
String name, {
Map<String, String> pathParameters = const <String, String>{},
Map<String, dynamic> queryParameters = const <String, dynamic>{},
Object? extra,
String? fragment,
}) {
this.name = name;
this.pathParameters = pathParameters;
this.queryParameters = queryParameters;
this.extra = extra;
this.fragment = fragment;
}
}

Expand Down

0 comments on commit ac08525

Please sign in to comment.