Skip to content

Commit

Permalink
[go_router] Feat add route redirect shellroutes (#114559) (#6432)
Browse files Browse the repository at this point in the history
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 <47866232+chunhtai@users.noreply.github.com>
Co-authored-by: Victor Ohashi <victor.ohashi@govoll.com>
  • Loading branch information
3 people committed May 9, 2024
1 parent 6c4482a commit 3fb302d
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 74 deletions.
4 changes: 4 additions & 0 deletions packages/go_router/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 14.1.0

- Adds route redirect to ShellRoutes

## 14.0.2

- Fixes unwanted logs when `hierarchicalLoggingEnabled` was set to `true`.
Expand Down
22 changes: 9 additions & 13 deletions packages/go_router/lib/src/configuration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -394,14 +394,13 @@ class RouteConfiguration {
return prevMatchList;
}

final List<RouteMatch> routeMatches = <RouteMatch>[];
final List<RouteMatchBase> routeMatches = <RouteMatchBase>[];
prevMatchList.visitRouteMatches((RouteMatchBase match) {
if (match is RouteMatch) {
if (match.route.redirect != null) {
routeMatches.add(match);
}
return true;
});

final FutureOr<String?> routeLevelRedirectResult =
_getRouteLevelRedirect(context, prevMatchList, routeMatches, 0);

Expand Down Expand Up @@ -434,25 +433,22 @@ class RouteConfiguration {
FutureOr<String?> _getRouteLevelRedirect(
BuildContext context,
RouteMatchList matchList,
List<RouteMatch> routeMatches,
List<RouteMatchBase> routeMatches,
int currentCheckIndex,
) {
if (currentCheckIndex >= routeMatches.length) {
return null;
}
final RouteMatch match = routeMatches[currentCheckIndex];
final RouteMatchBase match = routeMatches[currentCheckIndex];
FutureOr<String?> processRouteRedirect(String? newLocation) =>
newLocation ??
_getRouteLevelRedirect(
context, matchList, routeMatches, currentCheckIndex + 1);
final GoRoute route = match.route;
FutureOr<String?> routeRedirectResult;
if (route.redirect != null) {
routeRedirectResult = route.redirect!(
context,
match.buildState(this, matchList),
);
}
final RouteBase route = match.route;
final FutureOr<String?> routeRedirectResult = route.redirect!.call(
context,
match.buildState(this, matchList),
);
if (routeRedirectResult is String?) {
return processRouteRedirect(routeRedirectResult);
}
Expand Down
127 changes: 67 additions & 60 deletions packages/go_router/lib/src/route.dart
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,67 @@ typedef ExitCallback = FutureOr<bool> Function(
@immutable
abstract class RouteBase with Diagnosticable {
const RouteBase._({
this.redirect,
required this.routes,
required this.parentNavigatorKey,
});
/// An optional redirect function for this route.
///
/// In the case that you like to make a redirection decision for a specific
/// route (or sub-route), consider doing so by passing a redirect function to
/// the GoRoute constructor.
///
/// For example:
/// ```
/// final GoRouter _router = GoRouter(
/// routes: <GoRoute>[
/// GoRoute(
/// path: '/',
/// redirect: (_) => '/family/${Families.data[0].id}',
/// ),
/// GoRoute(
/// path: '/family/:fid',
/// pageBuilder: (BuildContext context, GoRouterState state) => ...,
/// ),
/// ],
/// );
/// ```
///
/// If there are multiple redirects in the matched routes, the parent route's
/// redirect takes priority over sub-route's.
///
/// For example:
/// ```
/// final GoRouter _router = GoRouter(
/// routes: <GoRoute>[
/// GoRoute(
/// path: '/',
/// redirect: (_) => '/page1', // this takes priority over the sub-route.
/// routes: <GoRoute>[
/// GoRoute(
/// path: 'child',
/// redirect: (_) => '/page2',
/// ),
/// ],
/// ),
/// ],
/// );
/// ```
///
/// The `context.go('/child')` will be redirected to `/page1` instead of
/// `/page2`.
///
/// Redirect can also be used for conditionally preventing users from visiting
/// routes, also known as route guards. One canonical example is user
/// authentication. See [Redirection](https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/redirection.dart)
/// for a complete runnable example.
///
/// If [BuildContext.dependOnInheritedWidgetOfExactType] is used during the
/// redirection (which is how `of` method is usually implemented), a
/// re-evaluation will be triggered if the [InheritedWidget] changes.
final GoRouterRedirect? redirect;
/// The list of child routes associated with this route.
final List<RouteBase> routes;
Expand Down Expand Up @@ -209,7 +266,7 @@ class GoRoute extends RouteBase {
this.builder,
this.pageBuilder,
super.parentNavigatorKey,
this.redirect,
super.redirect,
this.onExit,
super.routes = const <RouteBase>[],
}) : assert(path.isNotEmpty, 'GoRoute path cannot be empty'),
Expand Down Expand Up @@ -325,62 +382,6 @@ class GoRoute extends RouteBase {
///
final GoRouterWidgetBuilder? builder;
/// An optional redirect function for this route.
///
/// In the case that you like to make a redirection decision for a specific
/// route (or sub-route), consider doing so by passing a redirect function to
/// the GoRoute constructor.
///
/// For example:
/// ```
/// final GoRouter _router = GoRouter(
/// routes: <GoRoute>[
/// GoRoute(
/// path: '/',
/// redirect: (_) => '/family/${Families.data[0].id}',
/// ),
/// GoRoute(
/// path: '/family/:fid',
/// pageBuilder: (BuildContext context, GoRouterState state) => ...,
/// ),
/// ],
/// );
/// ```
///
/// If there are multiple redirects in the matched routes, the parent route's
/// redirect takes priority over sub-route's.
///
/// For example:
/// ```
/// final GoRouter _router = GoRouter(
/// routes: <GoRoute>[
/// GoRoute(
/// path: '/',
/// redirect: (_) => '/page1', // this takes priority over the sub-route.
/// routes: <GoRoute>[
/// GoRoute(
/// path: 'child',
/// redirect: (_) => '/page2',
/// ),
/// ],
/// ),
/// ],
/// );
/// ```
///
/// The `context.go('/child')` will be redirected to `/page1` instead of
/// `/page2`.
///
/// Redirect can also be used for conditionally preventing users from visiting
/// routes, also known as route guards. One canonical example is user
/// authentication. See [Redirection](https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/redirection.dart)
/// for a complete runnable example.
///
/// If [BuildContext.dependOnInheritedWidgetOfExactType] is used during the
/// redirection (which is how `of` method is usually implemented), a
/// re-evaluation will be triggered if the [InheritedWidget] changes.
final GoRouterRedirect? redirect;
/// Called when this route is removed from GoRouter's route history.
///
/// Some example this callback may be called:
Expand Down Expand Up @@ -458,9 +459,11 @@ class GoRoute extends RouteBase {
/// as [ShellRoute] and [StatefulShellRoute].
abstract class ShellRouteBase extends RouteBase {
/// Constructs a [ShellRouteBase].
const ShellRouteBase._(
{required super.routes, required super.parentNavigatorKey})
: super._();
const ShellRouteBase._({
super.redirect,
required super.routes,
required super.parentNavigatorKey,
}) : super._();
static void _debugCheckSubRouteParentNavigatorKeys(
List<RouteBase> subRoutes, GlobalKey<NavigatorState> navigatorKey) {
Expand Down Expand Up @@ -623,6 +626,7 @@ class ShellRouteContext {
class ShellRoute extends ShellRouteBase {
/// Constructs a [ShellRoute].
ShellRoute({
super.redirect,
this.builder,
this.pageBuilder,
this.observers,
Expand Down Expand Up @@ -783,6 +787,7 @@ class StatefulShellRoute extends ShellRouteBase {
/// [navigatorContainerBuilder].
StatefulShellRoute({
required this.branches,
super.redirect,
this.builder,
this.pageBuilder,
required this.navigatorContainerBuilder,
Expand All @@ -809,12 +814,14 @@ class StatefulShellRoute extends ShellRouteBase {
/// for a complete runnable example using StatefulShellRoute.indexedStack.
StatefulShellRoute.indexedStack({
required List<StatefulShellBranch> branches,
GoRouterRedirect? redirect,
StatefulShellRouteBuilder? builder,
GlobalKey<NavigatorState>? parentNavigatorKey,
StatefulShellRoutePageBuilder? pageBuilder,
String? restorationScopeId,
}) : this(
branches: branches,
redirect: redirect,
builder: builder,
pageBuilder: pageBuilder,
parentNavigatorKey: parentNavigatorKey,
Expand Down
2 changes: 1 addition & 1 deletion packages/go_router/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: go_router
description: A declarative router for Flutter based on Navigation 2 supporting
deep linking, data-driven routes and more
version: 14.0.2
version: 14.1.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
88 changes: 88 additions & 0 deletions packages/go_router/test/go_router_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2462,6 +2462,94 @@ void main() {
expect(
router.routerDelegate.currentConfiguration.uri.toString(), '/other');
});

testWidgets('redirect when go to a shell route',
(WidgetTester tester) async {
final List<RouteBase> routes = <RouteBase>[
ShellRoute(
redirect: (BuildContext context, GoRouterState state) => '/dummy',
builder: (BuildContext context, GoRouterState state, Widget child) =>
Scaffold(appBar: AppBar(), body: child),
routes: <RouteBase>[
GoRoute(
path: '/other',
builder: (BuildContext context, GoRouterState state) =>
const DummyScreen(),
),
GoRoute(
path: '/other2',
builder: (BuildContext context, GoRouterState state) =>
const DummyScreen(),
),
],
),
GoRoute(
path: '/dummy',
builder: (BuildContext context, GoRouterState state) =>
const DummyScreen(),
),
];

final GoRouter router = await createRouter(routes, tester);

for (final String shellRoute in <String>['/other', '/other2']) {
router.go(shellRoute);
await tester.pump();
expect(
router.routerDelegate.currentConfiguration.uri.toString(),
'/dummy',
);
}
});

testWidgets('redirect when go to a stateful shell route',
(WidgetTester tester) async {
final List<RouteBase> routes = <RouteBase>[
StatefulShellRoute.indexedStack(
redirect: (BuildContext context, GoRouterState state) => '/dummy',
builder: (BuildContext context, GoRouterState state,
StatefulNavigationShell navigationShell) {
return navigationShell;
},
branches: <StatefulShellBranch>[
StatefulShellBranch(
routes: <RouteBase>[
GoRoute(
path: '/other',
builder: (BuildContext context, GoRouterState state) =>
const DummyScreen(),
),
],
),
StatefulShellBranch(
routes: <RouteBase>[
GoRoute(
path: '/other2',
builder: (BuildContext context, GoRouterState state) =>
const DummyScreen(),
),
],
),
],
),
GoRoute(
path: '/dummy',
builder: (BuildContext context, GoRouterState state) =>
const DummyScreen(),
),
];

final GoRouter router = await createRouter(routes, tester);

for (final String shellRoute in <String>['/other', '/other2']) {
router.go(shellRoute);
await tester.pump();
expect(
router.routerDelegate.currentConfiguration.uri.toString(),
'/dummy',
);
}
});
});

group('initial location', () {
Expand Down

0 comments on commit 3fb302d

Please sign in to comment.