Skip to content

Commit 9c294ef

Browse files
authored
[go_router] Add support for preloading branches of StatefulShellRoute (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.
1 parent 5e90ce2 commit 9c294ef

File tree

9 files changed

+566
-67
lines changed

9 files changed

+566
-67
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.5.0
2+
3+
- Adds preload support to StatefulShellRoute, configurable via `preload` parameter on StatefulShellBranch.
4+
15
## 14.4.1
26

37
- Adds `missing_code_block_language_in_doc_comment` lint.

packages/go_router/doc/configuration.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,8 @@ branches: <StatefulShellBranch>[
192192
],
193193
),
194194
],
195+
// To enable preloading of the initial locations of branches, pass
196+
// 'true' for the parameter `preload` (false is default).
195197
),
196198
```
197199

packages/go_router/example/lib/others/custom_stateful_shell_route.dart

Lines changed: 56 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@ final GlobalKey<NavigatorState> _rootNavigatorKey =
1111
GlobalKey<NavigatorState>(debugLabel: 'root');
1212
final GlobalKey<NavigatorState> _tabANavigatorKey =
1313
GlobalKey<NavigatorState>(debugLabel: 'tabANav');
14+
final GlobalKey<NavigatorState> _tabBNavigatorKey =
15+
GlobalKey<NavigatorState>(debugLabel: 'tabBNav');
16+
final GlobalKey<NavigatorState> _tabB1NavigatorKey =
17+
GlobalKey<NavigatorState>(debugLabel: 'tabB1Nav');
18+
final GlobalKey<NavigatorState> _tabB2NavigatorKey =
19+
GlobalKey<NavigatorState>(debugLabel: 'tabB2Nav');
20+
1421
@visibleForTesting
1522
// ignore: public_member_api_docs
1623
final GlobalKey<TabbedRootScreenState> tabbedRootScreenKey =
@@ -87,11 +94,15 @@ class NestedTabNavigationExampleApp extends StatelessWidget {
8794

8895
// The route branch for the second tab of the bottom navigation bar.
8996
StatefulShellBranch(
97+
navigatorKey: _tabBNavigatorKey,
98+
// To enable preloading of the initial locations of branches, pass
99+
// `true` for the parameter `preload` (`false` is default).
100+
preload: true,
90101
// StatefulShellBranch will automatically use the first descendant
91102
// GoRoute as the initial location of the branch. If another route
92103
// is desired, specify the location of it using the defaultLocation
93104
// parameter.
94-
// defaultLocation: '/b2',
105+
// defaultLocation: '/b1',
95106
routes: <RouteBase>[
96107
StatefulShellRoute(
97108
builder: (BuildContext context, GoRouterState state,
@@ -119,44 +130,53 @@ class NestedTabNavigationExampleApp extends StatelessWidget {
119130
// This bottom tab uses a nested shell, wrapping sub routes in a
120131
// top TabBar.
121132
branches: <StatefulShellBranch>[
122-
StatefulShellBranch(routes: <GoRoute>[
123-
GoRoute(
124-
path: '/b1',
125-
builder: (BuildContext context, GoRouterState state) =>
126-
const TabScreen(
127-
label: 'B1', detailsPath: '/b1/details'),
128-
routes: <RouteBase>[
133+
StatefulShellBranch(
134+
navigatorKey: _tabB1NavigatorKey,
135+
routes: <GoRoute>[
129136
GoRoute(
130-
path: 'details',
137+
path: '/b1',
131138
builder:
132139
(BuildContext context, GoRouterState state) =>
133-
const DetailsScreen(
134-
label: 'B1',
135-
withScaffold: false,
136-
),
140+
const TabScreen(
141+
label: 'B1', detailsPath: '/b1/details'),
142+
routes: <RouteBase>[
143+
GoRoute(
144+
path: 'details',
145+
builder:
146+
(BuildContext context, GoRouterState state) =>
147+
const DetailsScreen(
148+
label: 'B1',
149+
withScaffold: false,
150+
),
151+
),
152+
],
137153
),
138-
],
139-
),
140-
]),
141-
StatefulShellBranch(routes: <GoRoute>[
142-
GoRoute(
143-
path: '/b2',
144-
builder: (BuildContext context, GoRouterState state) =>
145-
const TabScreen(
146-
label: 'B2', detailsPath: '/b2/details'),
147-
routes: <RouteBase>[
154+
]),
155+
StatefulShellBranch(
156+
navigatorKey: _tabB2NavigatorKey,
157+
// To enable preloading for all nested branches, set
158+
// `preload` to `true` (`false` is default).
159+
preload: true,
160+
routes: <GoRoute>[
148161
GoRoute(
149-
path: 'details',
162+
path: '/b2',
150163
builder:
151164
(BuildContext context, GoRouterState state) =>
152-
const DetailsScreen(
153-
label: 'B2',
154-
withScaffold: false,
155-
),
165+
const TabScreen(
166+
label: 'B2', detailsPath: '/b2/details'),
167+
routes: <RouteBase>[
168+
GoRoute(
169+
path: 'details',
170+
builder:
171+
(BuildContext context, GoRouterState state) =>
172+
const DetailsScreen(
173+
label: 'B2',
174+
withScaffold: false,
175+
),
176+
),
177+
],
156178
),
157-
],
158-
),
159-
]),
179+
]),
160180
],
161181
),
162182
],
@@ -619,6 +639,11 @@ class TabScreen extends StatelessWidget {
619639

620640
@override
621641
Widget build(BuildContext context) {
642+
/// If preloading is enabled on the top StatefulShellRoute, this will be
643+
/// printed directly after the app has been started, but only for the route
644+
/// that is the initial location ('/b1')
645+
debugPrint('Building TabScreen - $label');
646+
622647
return Center(
623648
child: Column(
624649
mainAxisSize: MainAxisSize.min,

packages/go_router/example/lib/stateful_shell_route.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ class NestedTabNavigationExampleApp extends StatelessWidget {
6363
],
6464
),
6565
],
66+
// To enable preloading of the initial locations of branches, pass
67+
// 'true' for the parameter `preload` (false is default).
6668
),
6769
// #enddocregion configuration-branches
6870

packages/go_router/lib/src/builder.dart

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ class RouteBuilder {
110110
return builderWithNav(
111111
context,
112112
_CustomNavigator(
113+
// The state needs to persist across rebuild.
114+
key: GlobalObjectKey(configuration.navigatorKey.hashCode),
113115
navigatorKey: configuration.navigatorKey,
114116
observers: observers,
115117
navigatorRestorationId: restorationScopeId,
@@ -271,16 +273,22 @@ class _CustomNavigatorState extends State<_CustomNavigator> {
271273
route: match.route,
272274
routerState: state,
273275
navigatorKey: navigatorKey,
276+
match: match,
274277
routeMatchList: widget.matchList,
275-
navigatorBuilder:
276-
(List<NavigatorObserver>? observers, String? restorationScopeId) {
278+
navigatorBuilder: (
279+
GlobalKey<NavigatorState> navigatorKey,
280+
ShellRouteMatch match,
281+
RouteMatchList matchList,
282+
List<NavigatorObserver>? observers,
283+
String? restorationScopeId,
284+
) {
277285
return _CustomNavigator(
278286
// The state needs to persist across rebuild.
279287
key: GlobalObjectKey(navigatorKey.hashCode),
280288
navigatorRestorationId: restorationScopeId,
281289
navigatorKey: navigatorKey,
282290
matches: match.matches,
283-
matchList: widget.matchList,
291+
matchList: matchList,
284292
configuration: widget.configuration,
285293
observers: observers ?? const <NavigatorObserver>[],
286294
onPopPageWithRouteMatch: widget.onPopPageWithRouteMatch,

0 commit comments

Comments
 (0)