Skip to content

Commit

Permalink
[go_router] Add support for preloading branches of StatefulShellRoute…
Browse files Browse the repository at this point in the history
… (revised solution) (#6467)

Adds support for preloading branches in a `StatefulShellRoute`. This functionality was initially part of an early implementation of #2650, however it was decided to implement this in a separate PR. The current implementation is a rewrite of the original implementation to better fit the final version of `StatefulShellRoute` (and go_router in general). 

**NOTE**: this is a revised version of the initial solution (see #4251), containing a substantially simpler implementation made possible thanks to recent refactoring in go_router.

This fixes issue flutter/flutter#127804.
  • Loading branch information
tolo authored Nov 12, 2024
1 parent 5e90ce2 commit 9c294ef
Show file tree
Hide file tree
Showing 9 changed files with 566 additions and 67 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.5.0

- Adds preload support to StatefulShellRoute, configurable via `preload` parameter on StatefulShellBranch.

## 14.4.1

- Adds `missing_code_block_language_in_doc_comment` lint.
Expand Down
2 changes: 2 additions & 0 deletions packages/go_router/doc/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ branches: <StatefulShellBranch>[
],
),
],
// To enable preloading of the initial locations of branches, pass
// 'true' for the parameter `preload` (false is default).
),
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ final GlobalKey<NavigatorState> _rootNavigatorKey =
GlobalKey<NavigatorState>(debugLabel: 'root');
final GlobalKey<NavigatorState> _tabANavigatorKey =
GlobalKey<NavigatorState>(debugLabel: 'tabANav');
final GlobalKey<NavigatorState> _tabBNavigatorKey =
GlobalKey<NavigatorState>(debugLabel: 'tabBNav');
final GlobalKey<NavigatorState> _tabB1NavigatorKey =
GlobalKey<NavigatorState>(debugLabel: 'tabB1Nav');
final GlobalKey<NavigatorState> _tabB2NavigatorKey =
GlobalKey<NavigatorState>(debugLabel: 'tabB2Nav');

@visibleForTesting
// ignore: public_member_api_docs
final GlobalKey<TabbedRootScreenState> tabbedRootScreenKey =
Expand Down Expand Up @@ -87,11 +94,15 @@ class NestedTabNavigationExampleApp extends StatelessWidget {

// The route branch for the second tab of the bottom navigation bar.
StatefulShellBranch(
navigatorKey: _tabBNavigatorKey,
// To enable preloading of the initial locations of branches, pass
// `true` for the parameter `preload` (`false` is default).
preload: true,
// StatefulShellBranch will automatically use the first descendant
// GoRoute as the initial location of the branch. If another route
// is desired, specify the location of it using the defaultLocation
// parameter.
// defaultLocation: '/b2',
// defaultLocation: '/b1',
routes: <RouteBase>[
StatefulShellRoute(
builder: (BuildContext context, GoRouterState state,
Expand Down Expand Up @@ -119,44 +130,53 @@ class NestedTabNavigationExampleApp extends StatelessWidget {
// This bottom tab uses a nested shell, wrapping sub routes in a
// top TabBar.
branches: <StatefulShellBranch>[
StatefulShellBranch(routes: <GoRoute>[
GoRoute(
path: '/b1',
builder: (BuildContext context, GoRouterState state) =>
const TabScreen(
label: 'B1', detailsPath: '/b1/details'),
routes: <RouteBase>[
StatefulShellBranch(
navigatorKey: _tabB1NavigatorKey,
routes: <GoRoute>[
GoRoute(
path: 'details',
path: '/b1',
builder:
(BuildContext context, GoRouterState state) =>
const DetailsScreen(
label: 'B1',
withScaffold: false,
),
const TabScreen(
label: 'B1', detailsPath: '/b1/details'),
routes: <RouteBase>[
GoRoute(
path: 'details',
builder:
(BuildContext context, GoRouterState state) =>
const DetailsScreen(
label: 'B1',
withScaffold: false,
),
),
],
),
],
),
]),
StatefulShellBranch(routes: <GoRoute>[
GoRoute(
path: '/b2',
builder: (BuildContext context, GoRouterState state) =>
const TabScreen(
label: 'B2', detailsPath: '/b2/details'),
routes: <RouteBase>[
]),
StatefulShellBranch(
navigatorKey: _tabB2NavigatorKey,
// To enable preloading for all nested branches, set
// `preload` to `true` (`false` is default).
preload: true,
routes: <GoRoute>[
GoRoute(
path: 'details',
path: '/b2',
builder:
(BuildContext context, GoRouterState state) =>
const DetailsScreen(
label: 'B2',
withScaffold: false,
),
const TabScreen(
label: 'B2', detailsPath: '/b2/details'),
routes: <RouteBase>[
GoRoute(
path: 'details',
builder:
(BuildContext context, GoRouterState state) =>
const DetailsScreen(
label: 'B2',
withScaffold: false,
),
),
],
),
],
),
]),
]),
],
),
],
Expand Down Expand Up @@ -619,6 +639,11 @@ class TabScreen extends StatelessWidget {

@override
Widget build(BuildContext context) {
/// If preloading is enabled on the top StatefulShellRoute, this will be
/// printed directly after the app has been started, but only for the route
/// that is the initial location ('/b1')
debugPrint('Building TabScreen - $label');

return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
Expand Down
2 changes: 2 additions & 0 deletions packages/go_router/example/lib/stateful_shell_route.dart
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ class NestedTabNavigationExampleApp extends StatelessWidget {
],
),
],
// To enable preloading of the initial locations of branches, pass
// 'true' for the parameter `preload` (false is default).
),
// #enddocregion configuration-branches

Expand Down
14 changes: 11 additions & 3 deletions packages/go_router/lib/src/builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ class RouteBuilder {
return builderWithNav(
context,
_CustomNavigator(
// The state needs to persist across rebuild.
key: GlobalObjectKey(configuration.navigatorKey.hashCode),
navigatorKey: configuration.navigatorKey,
observers: observers,
navigatorRestorationId: restorationScopeId,
Expand Down Expand Up @@ -271,16 +273,22 @@ class _CustomNavigatorState extends State<_CustomNavigator> {
route: match.route,
routerState: state,
navigatorKey: navigatorKey,
match: match,
routeMatchList: widget.matchList,
navigatorBuilder:
(List<NavigatorObserver>? observers, String? restorationScopeId) {
navigatorBuilder: (
GlobalKey<NavigatorState> navigatorKey,
ShellRouteMatch match,
RouteMatchList matchList,
List<NavigatorObserver>? observers,
String? restorationScopeId,
) {
return _CustomNavigator(
// The state needs to persist across rebuild.
key: GlobalObjectKey(navigatorKey.hashCode),
navigatorRestorationId: restorationScopeId,
navigatorKey: navigatorKey,
matches: match.matches,
matchList: widget.matchList,
matchList: matchList,
configuration: widget.configuration,
observers: observers ?? const <NavigatorObserver>[],
onPopPageWithRouteMatch: widget.onPopPageWithRouteMatch,
Expand Down
Loading

0 comments on commit 9c294ef

Please sign in to comment.