Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
## 8.1.0

- Adds parent navigator key to ShellRoute and StatefulShellRoute.

## 8.0.5

- Fixes a bug that GoRouterState in top level redirect doesn't contain complete data.
Expand Down
130 changes: 63 additions & 67 deletions packages/go_router/lib/src/builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -204,77 +204,73 @@ class RouteBuilder {
keyToPages.putIfAbsent(navigatorKey, () => <Page<Object?>>[]).add(page);
_buildRecursive(context, matchList, startIndex + 1, pagePopContext,
routerNeglect, keyToPages, navigatorKey, registry);
} else if (route is GoRoute) {
page = _buildPageForGoRoute(context, state, match, route, pagePopContext);
// If this GoRoute is for a different Navigator, add it to the
} else {
// If this RouteBase is for a different Navigator, add it to the
// list of out of scope pages
final GlobalKey<NavigatorState> goRouteNavKey =
final GlobalKey<NavigatorState> routeNavKey =
route.parentNavigatorKey ?? navigatorKey;

keyToPages.putIfAbsent(goRouteNavKey, () => <Page<Object?>>[]).add(page);

_buildRecursive(context, matchList, startIndex + 1, pagePopContext,
routerNeglect, keyToPages, navigatorKey, registry);
} else if (route is ShellRouteBase) {
assert(startIndex + 1 < matchList.matches.length,
'Shell routes must always have child routes');
// The key for the Navigator that will display this ShellRoute's page.
final GlobalKey<NavigatorState> parentNavigatorKey = navigatorKey;

// Add an entry for the parent navigator if none exists.
keyToPages.putIfAbsent(parentNavigatorKey, () => <Page<Object?>>[]);

// Calling _buildRecursive can result in adding pages to the
// parentNavigatorKey entry's list. Store the current length so
// that the page for this ShellRoute is placed at the right index.
final int shellPageIdx = keyToPages[parentNavigatorKey]!.length;

// Get the current sub-route of this shell route from the match list.
final RouteBase subRoute = matchList.matches[startIndex + 1].route;

// The key to provide to the shell route's Navigator.
final GlobalKey<NavigatorState> shellNavigatorKey =
route.navigatorKeyForSubRoute(subRoute);

// Add an entry for the shell route's navigator
keyToPages.putIfAbsent(shellNavigatorKey, () => <Page<Object?>>[]);

// Build the remaining pages
_buildRecursive(context, matchList, startIndex + 1, pagePopContext,
routerNeglect, keyToPages, shellNavigatorKey, registry);

final HeroController heroController = _goHeroCache.putIfAbsent(
shellNavigatorKey, () => _getHeroController(context));

// Build the Navigator for this shell route
Widget buildShellNavigator(
List<NavigatorObserver>? observers, String? restorationScopeId) {
return _buildNavigator(
pagePopContext.onPopPage,
keyToPages[shellNavigatorKey]!,
shellNavigatorKey,
observers: observers ?? const <NavigatorObserver>[],
restorationScopeId: restorationScopeId,
heroController: heroController,
if (route is GoRoute) {
page =
_buildPageForGoRoute(context, state, match, route, pagePopContext);

keyToPages.putIfAbsent(routeNavKey, () => <Page<Object?>>[]).add(page);

_buildRecursive(context, matchList, startIndex + 1, pagePopContext,
routerNeglect, keyToPages, navigatorKey, registry);
} else if (route is ShellRouteBase) {
assert(startIndex + 1 < matchList.matches.length,
'Shell routes must always have child routes');

// Add an entry for the parent navigator if none exists.
//
// Calling _buildRecursive can result in adding pages to the
// parentNavigatorKey entry's list. Store the current length so
// that the page for this ShellRoute is placed at the right index.
final int shellPageIdx =
keyToPages.putIfAbsent(routeNavKey, () => <Page<Object?>>[]).length;

// Find the the navigator key for the sub-route of this shell route.
final RouteBase subRoute = matchList.matches[startIndex + 1].route;
final GlobalKey<NavigatorState> shellNavigatorKey =
route.navigatorKeyForSubRoute(subRoute);

keyToPages.putIfAbsent(shellNavigatorKey, () => <Page<Object?>>[]);

// Build the remaining pages
_buildRecursive(context, matchList, startIndex + 1, pagePopContext,
routerNeglect, keyToPages, shellNavigatorKey, registry);

final HeroController heroController = _goHeroCache.putIfAbsent(
shellNavigatorKey, () => _getHeroController(context));

// Build the Navigator for this shell route
Widget buildShellNavigator(
List<NavigatorObserver>? observers, String? restorationScopeId) {
return _buildNavigator(
pagePopContext.onPopPage,
keyToPages[shellNavigatorKey]!,
shellNavigatorKey,
observers: observers ?? const <NavigatorObserver>[],
restorationScopeId: restorationScopeId,
heroController: heroController,
);
}

// Call the ShellRouteBase to create/update the shell route state
final ShellRouteContext shellRouteContext = ShellRouteContext(
route: route,
routerState: state,
navigatorKey: shellNavigatorKey,
routeMatchList: matchList,
navigatorBuilder: buildShellNavigator,
);
}

// Call the ShellRouteBase to create/update the shell route state
final ShellRouteContext shellRouteContext = ShellRouteContext(
route: route,
routerState: state,
navigatorKey: shellNavigatorKey,
routeMatchList: matchList,
navigatorBuilder: buildShellNavigator,
);

// Build the Page for this route
page = _buildPageForShellRoute(
context, state, match, route, pagePopContext, shellRouteContext);
// Place the ShellRoute's Page onto the list for the parent navigator.
keyToPages
.putIfAbsent(parentNavigatorKey, () => <Page<Object?>>[])
.insert(shellPageIdx, page);
// Build the Page for this route
page = _buildPageForShellRoute(
context, state, match, route, pagePopContext, shellRouteContext);
// Place the ShellRoute's Page onto the list for the parent navigator.
keyToPages[routeNavKey]!.insert(shellPageIdx, page);
}
}
if (page != null) {
registry[page] = state;
Expand Down
82 changes: 35 additions & 47 deletions packages/go_router/lib/src/delegate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -148,73 +148,61 @@ class GoRouterDelegate extends RouterDelegate<RouteMatchList>
/// pageless route, such as a dialog or bottom sheet.
class _NavigatorStateIterator extends Iterator<NavigatorState> {
_NavigatorStateIterator(this.matchList, this.root)
: index = matchList.matches.length;
: index = matchList.matches.length - 1;

final RouteMatchList matchList;
int index = 0;
int index;

final NavigatorState root;
@override
late NavigatorState current;

RouteBase _getRouteAtIndex(int index) => matchList.matches[index].route;

void _findsNextIndex() {
final GlobalKey<NavigatorState>? parentNavigatorKey =
_getRouteAtIndex(index).parentNavigatorKey;
if (parentNavigatorKey == null) {
index -= 1;
return;
}

for (index -= 1; index >= 0; index -= 1) {
final RouteBase route = _getRouteAtIndex(index);
if (route is ShellRouteBase) {
if (route.navigatorKeyForSubRoute(_getRouteAtIndex(index + 1)) ==
parentNavigatorKey) {
return;
}
}
}
assert(root == parentNavigatorKey.currentState);
}

@override
bool moveNext() {
if (index < 0) {
return false;
}
late RouteBase subRoute;
for (index -= 1; index >= 0; index -= 1) {
final RouteMatch match = matchList.matches[index];
final RouteBase route = match.route;
if (route is GoRoute && route.parentNavigatorKey != null) {
final GlobalKey<NavigatorState> parentNavigatorKey =
route.parentNavigatorKey!;
final ModalRoute<Object?>? parentModalRoute =
ModalRoute.of(parentNavigatorKey.currentContext!);
// The ModalRoute can be null if the parentNavigatorKey references the
// root navigator.
if (parentModalRoute == null) {
index = -1;
assert(root == parentNavigatorKey.currentState);
current = root;
return true;
}
// It must be a ShellRoute that holds this parentNavigatorKey;
// otherwise, parentModalRoute would have been null. Updates the index
// to the ShellRoute
for (index -= 1; index >= 0; index -= 1) {
final RouteBase route = matchList.matches[index].route;
if (route is ShellRoute) {
if (route.navigatorKey == parentNavigatorKey) {
break;
}
}
}
// There may be a pageless route on top of ModalRoute that the
// NavigatorState of parentNavigatorKey is in. For example, an open
// dialog. In that case we want to find the navigator that host the
// pageless route.
if (parentModalRoute.isCurrent == false) {
continue;
}
_findsNextIndex();

current = parentNavigatorKey.currentState!;
return true;
} else if (route is ShellRouteBase) {
while (index >= 0) {
final RouteBase route = _getRouteAtIndex(index);
if (route is ShellRouteBase) {
final GlobalKey<NavigatorState> navigatorKey =
route.navigatorKeyForSubRoute(_getRouteAtIndex(index + 1));
// Must have a ModalRoute parent because the navigator ShellRoute
// created must not be the root navigator.
final GlobalKey<NavigatorState> navigatorKey =
route.navigatorKeyForSubRoute(subRoute);
final ModalRoute<Object?> parentModalRoute =
ModalRoute.of(navigatorKey.currentContext!)!;
// There may be pageless route on top of ModalRoute that the
// parentNavigatorKey is in. For example an open dialog.
if (parentModalRoute.isCurrent == false) {
continue;
if (parentModalRoute.isCurrent) {
current = navigatorKey.currentState!;
return true;
}
current = navigatorKey.currentState!;
return true;
}
subRoute = route;
_findsNextIndex();
}
assert(index == -1);
current = root;
Expand Down
29 changes: 18 additions & 11 deletions packages/go_router/lib/src/route.dart
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,20 @@ import 'typedefs.dart';
@immutable
abstract class RouteBase {
const RouteBase._({
this.routes = const <RouteBase>[],
required this.routes,
required this.parentNavigatorKey,
});

/// The list of child routes associated with this route.
final List<RouteBase> routes;

/// An optional key specifying which Navigator to display this route's screen
/// onto.
///
/// Specifying the root Navigator will stack this route onto that
/// Navigator instead of the nearest ShellRoute ancestor.
final GlobalKey<NavigatorState>? parentNavigatorKey;

/// Builds a lists containing the provided routes along with all their
/// descendant [routes].
static Iterable<RouteBase> routesRecursively(Iterable<RouteBase> routes) {
Expand Down Expand Up @@ -137,7 +145,7 @@ class GoRoute extends RouteBase {
this.name,
this.builder,
this.pageBuilder,
this.parentNavigatorKey,
super.parentNavigatorKey,
this.redirect,
super.routes = const <RouteBase>[],
}) : assert(path.isNotEmpty, 'GoRoute path cannot be empty'),
Expand Down Expand Up @@ -301,13 +309,6 @@ class GoRoute extends RouteBase {
/// re-evaluation will be triggered if the [InheritedWidget] changes.
final GoRouterRedirect? redirect;

/// An optional key specifying which Navigator to display this route's screen
/// onto.
///
/// Specifying the root Navigator will stack this route onto that
/// Navigator instead of the nearest ShellRoute ancestor.
final GlobalKey<NavigatorState>? parentNavigatorKey;

// TODO(chunhtai): move all regex related help methods to path_utils.dart.
/// Match this route against a location.
RegExpMatch? matchPatternAsPrefix(String loc) =>
Expand All @@ -333,7 +334,9 @@ class GoRoute extends RouteBase {
/// as [ShellRoute] and [StatefulShellRoute].
abstract class ShellRouteBase extends RouteBase {
/// Constructs a [ShellRouteBase].
const ShellRouteBase._({super.routes}) : super._();
const ShellRouteBase._(
{required super.routes, required super.parentNavigatorKey})
: super._();

/// Attempts to build the Widget representing this shell route.
///
Expand Down Expand Up @@ -496,7 +499,8 @@ class ShellRoute extends ShellRouteBase {
this.builder,
this.pageBuilder,
this.observers,
super.routes,
required super.routes,
super.parentNavigatorKey,
GlobalKey<NavigatorState>? navigatorKey,
this.restorationScopeId,
}) : assert(routes.isNotEmpty),
Expand Down Expand Up @@ -653,6 +657,7 @@ class StatefulShellRoute extends ShellRouteBase {
this.builder,
this.pageBuilder,
required this.navigatorContainerBuilder,
super.parentNavigatorKey,
this.restorationScopeId,
}) : assert(branches.isNotEmpty),
assert((pageBuilder != null) ^ (builder != null),
Expand All @@ -676,12 +681,14 @@ class StatefulShellRoute extends ShellRouteBase {
StatefulShellRoute.indexedStack({
required List<StatefulShellBranch> branches,
StatefulShellRouteBuilder? builder,
GlobalKey<NavigatorState>? parentNavigatorKey,
StatefulShellRoutePageBuilder? pageBuilder,
String? restorationScopeId,
}) : this(
branches: branches,
builder: builder,
pageBuilder: pageBuilder,
parentNavigatorKey: parentNavigatorKey,
restorationScopeId: restorationScopeId,
navigatorContainerBuilder: _indexedStackContainerBuilder,
);
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: 8.0.5
version: 8.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
Loading