Skip to content

Commit 3fb302d

Browse files
VictorOhashichunhtaiVictor Ohashi
authored
[go_router] Feat add route redirect shellroutes (#114559) (#6432)
Possible solution for: flutter/flutter#114559 ## Pre-launch Checklist - [X] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [X] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [X] 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]. - [X] The title of the PR starts with the name of the package surrounded by square brackets, e.g. `[shared_preferences]` - [X] I [linked to at least one issue that this PR fixes] in the description above. - [X] I updated `pubspec.yaml` with an appropriate new version according to the [pub versioning philosophy], or this PR is [exempt from version changes]. - [X] I updated `CHANGELOG.md` to add a description of the change, [following repository CHANGELOG style]. - [ ] I updated/added relevant documentation (doc comments with `///`). - [X] I added new tests to check the change I am making, or this PR is [test-exempt]. - [X] All existing and new tests are passing. --------- Co-authored-by: chunhtai <[email protected]> Co-authored-by: Victor Ohashi <[email protected]>
1 parent 6c4482a commit 3fb302d

File tree

5 files changed

+169
-74
lines changed

5 files changed

+169
-74
lines changed

packages/go_router/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 14.1.0
2+
3+
- Adds route redirect to ShellRoutes
4+
15
## 14.0.2
26

37
- Fixes unwanted logs when `hierarchicalLoggingEnabled` was set to `true`.

packages/go_router/lib/src/configuration.dart

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -394,14 +394,13 @@ class RouteConfiguration {
394394
return prevMatchList;
395395
}
396396

397-
final List<RouteMatch> routeMatches = <RouteMatch>[];
397+
final List<RouteMatchBase> routeMatches = <RouteMatchBase>[];
398398
prevMatchList.visitRouteMatches((RouteMatchBase match) {
399-
if (match is RouteMatch) {
399+
if (match.route.redirect != null) {
400400
routeMatches.add(match);
401401
}
402402
return true;
403403
});
404-
405404
final FutureOr<String?> routeLevelRedirectResult =
406405
_getRouteLevelRedirect(context, prevMatchList, routeMatches, 0);
407406

@@ -434,25 +433,22 @@ class RouteConfiguration {
434433
FutureOr<String?> _getRouteLevelRedirect(
435434
BuildContext context,
436435
RouteMatchList matchList,
437-
List<RouteMatch> routeMatches,
436+
List<RouteMatchBase> routeMatches,
438437
int currentCheckIndex,
439438
) {
440439
if (currentCheckIndex >= routeMatches.length) {
441440
return null;
442441
}
443-
final RouteMatch match = routeMatches[currentCheckIndex];
442+
final RouteMatchBase match = routeMatches[currentCheckIndex];
444443
FutureOr<String?> processRouteRedirect(String? newLocation) =>
445444
newLocation ??
446445
_getRouteLevelRedirect(
447446
context, matchList, routeMatches, currentCheckIndex + 1);
448-
final GoRoute route = match.route;
449-
FutureOr<String?> routeRedirectResult;
450-
if (route.redirect != null) {
451-
routeRedirectResult = route.redirect!(
452-
context,
453-
match.buildState(this, matchList),
454-
);
455-
}
447+
final RouteBase route = match.route;
448+
final FutureOr<String?> routeRedirectResult = route.redirect!.call(
449+
context,
450+
match.buildState(this, matchList),
451+
);
456452
if (routeRedirectResult is String?) {
457453
return processRouteRedirect(routeRedirectResult);
458454
}

packages/go_router/lib/src/route.dart

Lines changed: 67 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,67 @@ typedef ExitCallback = FutureOr<bool> Function(
152152
@immutable
153153
abstract class RouteBase with Diagnosticable {
154154
const RouteBase._({
155+
this.redirect,
155156
required this.routes,
156157
required this.parentNavigatorKey,
157158
});
158159

160+
/// An optional redirect function for this route.
161+
///
162+
/// In the case that you like to make a redirection decision for a specific
163+
/// route (or sub-route), consider doing so by passing a redirect function to
164+
/// the GoRoute constructor.
165+
///
166+
/// For example:
167+
/// ```
168+
/// final GoRouter _router = GoRouter(
169+
/// routes: <GoRoute>[
170+
/// GoRoute(
171+
/// path: '/',
172+
/// redirect: (_) => '/family/${Families.data[0].id}',
173+
/// ),
174+
/// GoRoute(
175+
/// path: '/family/:fid',
176+
/// pageBuilder: (BuildContext context, GoRouterState state) => ...,
177+
/// ),
178+
/// ],
179+
/// );
180+
/// ```
181+
///
182+
/// If there are multiple redirects in the matched routes, the parent route's
183+
/// redirect takes priority over sub-route's.
184+
///
185+
/// For example:
186+
/// ```
187+
/// final GoRouter _router = GoRouter(
188+
/// routes: <GoRoute>[
189+
/// GoRoute(
190+
/// path: '/',
191+
/// redirect: (_) => '/page1', // this takes priority over the sub-route.
192+
/// routes: <GoRoute>[
193+
/// GoRoute(
194+
/// path: 'child',
195+
/// redirect: (_) => '/page2',
196+
/// ),
197+
/// ],
198+
/// ),
199+
/// ],
200+
/// );
201+
/// ```
202+
///
203+
/// The `context.go('/child')` will be redirected to `/page1` instead of
204+
/// `/page2`.
205+
///
206+
/// Redirect can also be used for conditionally preventing users from visiting
207+
/// routes, also known as route guards. One canonical example is user
208+
/// authentication. See [Redirection](https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/redirection.dart)
209+
/// for a complete runnable example.
210+
///
211+
/// If [BuildContext.dependOnInheritedWidgetOfExactType] is used during the
212+
/// redirection (which is how `of` method is usually implemented), a
213+
/// re-evaluation will be triggered if the [InheritedWidget] changes.
214+
final GoRouterRedirect? redirect;
215+
159216
/// The list of child routes associated with this route.
160217
final List<RouteBase> routes;
161218

@@ -209,7 +266,7 @@ class GoRoute extends RouteBase {
209266
this.builder,
210267
this.pageBuilder,
211268
super.parentNavigatorKey,
212-
this.redirect,
269+
super.redirect,
213270
this.onExit,
214271
super.routes = const <RouteBase>[],
215272
}) : assert(path.isNotEmpty, 'GoRoute path cannot be empty'),
@@ -325,62 +382,6 @@ class GoRoute extends RouteBase {
325382
///
326383
final GoRouterWidgetBuilder? builder;
327384

328-
/// An optional redirect function for this route.
329-
///
330-
/// In the case that you like to make a redirection decision for a specific
331-
/// route (or sub-route), consider doing so by passing a redirect function to
332-
/// the GoRoute constructor.
333-
///
334-
/// For example:
335-
/// ```
336-
/// final GoRouter _router = GoRouter(
337-
/// routes: <GoRoute>[
338-
/// GoRoute(
339-
/// path: '/',
340-
/// redirect: (_) => '/family/${Families.data[0].id}',
341-
/// ),
342-
/// GoRoute(
343-
/// path: '/family/:fid',
344-
/// pageBuilder: (BuildContext context, GoRouterState state) => ...,
345-
/// ),
346-
/// ],
347-
/// );
348-
/// ```
349-
///
350-
/// If there are multiple redirects in the matched routes, the parent route's
351-
/// redirect takes priority over sub-route's.
352-
///
353-
/// For example:
354-
/// ```
355-
/// final GoRouter _router = GoRouter(
356-
/// routes: <GoRoute>[
357-
/// GoRoute(
358-
/// path: '/',
359-
/// redirect: (_) => '/page1', // this takes priority over the sub-route.
360-
/// routes: <GoRoute>[
361-
/// GoRoute(
362-
/// path: 'child',
363-
/// redirect: (_) => '/page2',
364-
/// ),
365-
/// ],
366-
/// ),
367-
/// ],
368-
/// );
369-
/// ```
370-
///
371-
/// The `context.go('/child')` will be redirected to `/page1` instead of
372-
/// `/page2`.
373-
///
374-
/// Redirect can also be used for conditionally preventing users from visiting
375-
/// routes, also known as route guards. One canonical example is user
376-
/// authentication. See [Redirection](https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/redirection.dart)
377-
/// for a complete runnable example.
378-
///
379-
/// If [BuildContext.dependOnInheritedWidgetOfExactType] is used during the
380-
/// redirection (which is how `of` method is usually implemented), a
381-
/// re-evaluation will be triggered if the [InheritedWidget] changes.
382-
final GoRouterRedirect? redirect;
383-
384385
/// Called when this route is removed from GoRouter's route history.
385386
///
386387
/// Some example this callback may be called:
@@ -458,9 +459,11 @@ class GoRoute extends RouteBase {
458459
/// as [ShellRoute] and [StatefulShellRoute].
459460
abstract class ShellRouteBase extends RouteBase {
460461
/// Constructs a [ShellRouteBase].
461-
const ShellRouteBase._(
462-
{required super.routes, required super.parentNavigatorKey})
463-
: super._();
462+
const ShellRouteBase._({
463+
super.redirect,
464+
required super.routes,
465+
required super.parentNavigatorKey,
466+
}) : super._();
464467

465468
static void _debugCheckSubRouteParentNavigatorKeys(
466469
List<RouteBase> subRoutes, GlobalKey<NavigatorState> navigatorKey) {
@@ -623,6 +626,7 @@ class ShellRouteContext {
623626
class ShellRoute extends ShellRouteBase {
624627
/// Constructs a [ShellRoute].
625628
ShellRoute({
629+
super.redirect,
626630
this.builder,
627631
this.pageBuilder,
628632
this.observers,
@@ -783,6 +787,7 @@ class StatefulShellRoute extends ShellRouteBase {
783787
/// [navigatorContainerBuilder].
784788
StatefulShellRoute({
785789
required this.branches,
790+
super.redirect,
786791
this.builder,
787792
this.pageBuilder,
788793
required this.navigatorContainerBuilder,
@@ -809,12 +814,14 @@ class StatefulShellRoute extends ShellRouteBase {
809814
/// for a complete runnable example using StatefulShellRoute.indexedStack.
810815
StatefulShellRoute.indexedStack({
811816
required List<StatefulShellBranch> branches,
817+
GoRouterRedirect? redirect,
812818
StatefulShellRouteBuilder? builder,
813819
GlobalKey<NavigatorState>? parentNavigatorKey,
814820
StatefulShellRoutePageBuilder? pageBuilder,
815821
String? restorationScopeId,
816822
}) : this(
817823
branches: branches,
824+
redirect: redirect,
818825
builder: builder,
819826
pageBuilder: pageBuilder,
820827
parentNavigatorKey: parentNavigatorKey,

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: 14.0.2
4+
version: 14.1.0
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/go_router_test.dart

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2462,6 +2462,94 @@ void main() {
24622462
expect(
24632463
router.routerDelegate.currentConfiguration.uri.toString(), '/other');
24642464
});
2465+
2466+
testWidgets('redirect when go to a shell route',
2467+
(WidgetTester tester) async {
2468+
final List<RouteBase> routes = <RouteBase>[
2469+
ShellRoute(
2470+
redirect: (BuildContext context, GoRouterState state) => '/dummy',
2471+
builder: (BuildContext context, GoRouterState state, Widget child) =>
2472+
Scaffold(appBar: AppBar(), body: child),
2473+
routes: <RouteBase>[
2474+
GoRoute(
2475+
path: '/other',
2476+
builder: (BuildContext context, GoRouterState state) =>
2477+
const DummyScreen(),
2478+
),
2479+
GoRoute(
2480+
path: '/other2',
2481+
builder: (BuildContext context, GoRouterState state) =>
2482+
const DummyScreen(),
2483+
),
2484+
],
2485+
),
2486+
GoRoute(
2487+
path: '/dummy',
2488+
builder: (BuildContext context, GoRouterState state) =>
2489+
const DummyScreen(),
2490+
),
2491+
];
2492+
2493+
final GoRouter router = await createRouter(routes, tester);
2494+
2495+
for (final String shellRoute in <String>['/other', '/other2']) {
2496+
router.go(shellRoute);
2497+
await tester.pump();
2498+
expect(
2499+
router.routerDelegate.currentConfiguration.uri.toString(),
2500+
'/dummy',
2501+
);
2502+
}
2503+
});
2504+
2505+
testWidgets('redirect when go to a stateful shell route',
2506+
(WidgetTester tester) async {
2507+
final List<RouteBase> routes = <RouteBase>[
2508+
StatefulShellRoute.indexedStack(
2509+
redirect: (BuildContext context, GoRouterState state) => '/dummy',
2510+
builder: (BuildContext context, GoRouterState state,
2511+
StatefulNavigationShell navigationShell) {
2512+
return navigationShell;
2513+
},
2514+
branches: <StatefulShellBranch>[
2515+
StatefulShellBranch(
2516+
routes: <RouteBase>[
2517+
GoRoute(
2518+
path: '/other',
2519+
builder: (BuildContext context, GoRouterState state) =>
2520+
const DummyScreen(),
2521+
),
2522+
],
2523+
),
2524+
StatefulShellBranch(
2525+
routes: <RouteBase>[
2526+
GoRoute(
2527+
path: '/other2',
2528+
builder: (BuildContext context, GoRouterState state) =>
2529+
const DummyScreen(),
2530+
),
2531+
],
2532+
),
2533+
],
2534+
),
2535+
GoRoute(
2536+
path: '/dummy',
2537+
builder: (BuildContext context, GoRouterState state) =>
2538+
const DummyScreen(),
2539+
),
2540+
];
2541+
2542+
final GoRouter router = await createRouter(routes, tester);
2543+
2544+
for (final String shellRoute in <String>['/other', '/other2']) {
2545+
router.go(shellRoute);
2546+
await tester.pump();
2547+
expect(
2548+
router.routerDelegate.currentConfiguration.uri.toString(),
2549+
'/dummy',
2550+
);
2551+
}
2552+
});
24652553
});
24662554

24672555
group('initial location', () {

0 commit comments

Comments
 (0)