Skip to content

Commit

Permalink
[go_router] Documentation for StatefulShellRoute (#6308)
Browse files Browse the repository at this point in the history
Adds documentation about StatefulShellRoute to the Configuration topic (configuration.md).

Fixes flutter/flutter#127209.
  • Loading branch information
tolo authored Aug 1, 2024
1 parent 1b9195b commit 9cc06ac
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 4 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.2.2

- Adds section for "Stateful nested navigation" to configuration.md.

## 14.2.1

- Makes GoRouterState lookup more robust.
Expand Down
100 changes: 100 additions & 0 deletions packages/go_router/doc/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,105 @@ For a complete example, see the [ShellRoute
sample](https://github.com/flutter/packages/tree/main/packages/go_router/example/lib/shell_route.dart)
in the example/ directory.

# Stateful nested navigation
In addition to using nested navigation with for instance a BottomNavigationBar,
many apps also require that state is maintained when navigating between
destinations. To accomplish this, use [StatefulShellRoute][] instead of
`ShellRoute`.

StatefulShellRoute creates separate `Navigator`s for each of its nested [branches](https://pub.dev/documentation/go_router/latest/go_router/StatefulShellBranch-class.html)
(i.e. parallel navigation trees), making it possible to build an app with
stateful nested navigation. The constructor [StatefulShellRoute.indexedStack](https://pub.dev/documentation/go_router/latest/go_router/StatefulShellRoute/StatefulShellRoute.indexedStack.html)
provides a default implementation for managing the branch navigators, using an
`IndexedStack`.

When using StatefulShellRoute, routes aren't configured on the shell route
itself. Instead, they are configured for each of the branches. Example:

<?code-excerpt "../example/lib/stateful_shell_route.dart (configuration-branches)"?>
```dart
branches: <StatefulShellBranch>[
// The route branch for the first tab of the bottom navigation bar.
StatefulShellBranch(
navigatorKey: _sectionANavigatorKey,
routes: <RouteBase>[
GoRoute(
// The screen to display as the root in the first tab of the
// bottom navigation bar.
path: '/a',
builder: (BuildContext context, GoRouterState state) =>
const RootScreen(label: 'A', detailsPath: '/a/details'),
routes: <RouteBase>[
// The details screen to display stacked on navigator of the
// first tab. This will cover screen A but not the application
// shell (bottom navigation bar).
GoRoute(
path: 'details',
builder: (BuildContext context, GoRouterState state) =>
const DetailsScreen(label: 'A'),
),
],
),
],
),
```

Similar to ShellRoute, a builder must be provided to build the actual shell
Widget that encapsulates the branch navigation container. The latter is
implemented by the class [StatefulNavigationShell](https://pub.dev/documentation/go_router/latest/go_router/StatefulNavigationShell-class.html),
which is passed as the last argument to the builder function. Example:

<?code-excerpt "../example/lib/stateful_shell_route.dart (configuration-builder)"?>
```dart
StatefulShellRoute.indexedStack(
builder: (BuildContext context, GoRouterState state,
StatefulNavigationShell navigationShell) {
// Return the widget that implements the custom shell (in this case
// using a BottomNavigationBar). The StatefulNavigationShell is passed
// to be able access the state of the shell and to navigate to other
// branches in a stateful way.
return ScaffoldWithNavBar(navigationShell: navigationShell);
},
```

Within the custom shell widget, the StatefulNavigationShell is first and
foremost used as the child, or body, of the shell. Secondly, it is also used for
handling stateful switching between branches, as well as providing the currently
active branch index. Example:

<?code-excerpt "../example/lib/stateful_shell_route.dart (configuration-custom-shell)"?>
```dart
@override
Widget build(BuildContext context) {
return Scaffold(
// The StatefulNavigationShell from the associated StatefulShellRoute is
// directly passed as the body of the Scaffold.
body: navigationShell,
bottomNavigationBar: BottomNavigationBar(
// Here, the items of BottomNavigationBar are hard coded. In a real
// world scenario, the items would most likely be generated from the
// branches of the shell route, which can be fetched using
// `navigationShell.route.branches`.
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Section A'),
BottomNavigationBarItem(icon: Icon(Icons.work), label: 'Section B'),
BottomNavigationBarItem(icon: Icon(Icons.tab), label: 'Section C'),
],
currentIndex: navigationShell.currentIndex,
// Navigate to the current location of the branch at the provided index
// when tapping an item in the BottomNavigationBar.
onTap: (int index) => navigationShell.goBranch(index),
),
);
}
```

For a complete example, see the [Stateful Nested
Navigation](https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/stateful_shell_route.dart)
in the example/ directory.
For further details, see the [StatefulShellRoute API
documentation](https://pub.dev/documentation/go_router/latest/go_router/StatefulShellRoute-class.html).

# Initial location

The initial location is shown when the app first opens and there is no deep link
Expand Down Expand Up @@ -181,3 +280,4 @@ final _router = GoRouter(
[GoRoute]: https://pub.dev/documentation/go_router/latest/go_router/GoRoute-class.html
[GoRouterState]: https://pub.dev/documentation/go_router/latest/go_router/GoRouterState-class.html
[ShellRoute]: https://pub.dev/documentation/go_router/latest/go_router/ShellRoute-class.html
[StatefulShellRoute]: https://pub.dev/documentation/go_router/latest/go_router/StatefulShellRoute-class.html
18 changes: 15 additions & 3 deletions packages/go_router/example/lib/stateful_shell_route.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class NestedTabNavigationExampleApp extends StatelessWidget {
navigatorKey: _rootNavigatorKey,
initialLocation: '/a',
routes: <RouteBase>[
// #docregion configuration-builder
StatefulShellRoute.indexedStack(
builder: (BuildContext context, GoRouterState state,
StatefulNavigationShell navigationShell) {
Expand All @@ -37,6 +38,8 @@ class NestedTabNavigationExampleApp extends StatelessWidget {
// branches in a stateful way.
return ScaffoldWithNavBar(navigationShell: navigationShell);
},
// #enddocregion configuration-builder
// #docregion configuration-branches
branches: <StatefulShellBranch>[
// The route branch for the first tab of the bottom navigation bar.
StatefulShellBranch(
Expand All @@ -61,6 +64,7 @@ class NestedTabNavigationExampleApp extends StatelessWidget {
),
],
),
// #enddocregion configuration-branches

// The route branch for the second tab of the bottom navigation bar.
StatefulShellBranch(
Expand Down Expand Up @@ -145,9 +149,12 @@ class ScaffoldWithNavBar extends StatelessWidget {
/// The navigation shell and container for the branch Navigators.
final StatefulNavigationShell navigationShell;

// #docregion configuration-custom-shell
@override
Widget build(BuildContext context) {
return Scaffold(
// The StatefulNavigationShell from the associated StatefulShellRoute is
// directly passed as the body of the Scaffold.
body: navigationShell,
bottomNavigationBar: BottomNavigationBar(
// Here, the items of BottomNavigationBar are hard coded. In a real
Expand All @@ -160,13 +167,18 @@ class ScaffoldWithNavBar extends StatelessWidget {
BottomNavigationBarItem(icon: Icon(Icons.tab), label: 'Section C'),
],
currentIndex: navigationShell.currentIndex,
onTap: (int index) => _onTap(context, index),
// Navigate to the current location of the branch at the provided index
// when tapping an item in the BottomNavigationBar.
onTap: (int index) => navigationShell.goBranch(index),
),
);
}
// #enddocregion configuration-custom-shell

/// Navigate to the current location of the branch at the provided index when
/// tapping an item in the BottomNavigationBar.
/// NOTE: For a slightly more sophisticated branch switching, change the onTap
/// handler on the BottomNavigationBar above to the following:
/// `onTap: (int index) => _onTap(context, index),`
// ignore: unused_element
void _onTap(BuildContext context, int index) {
// When navigating to a new branch, it's recommended to use the goBranch
// method, as doing so makes sure the last navigation state of the
Expand Down
2 changes: 2 additions & 0 deletions packages/go_router/lib/src/route.dart
Original file line number Diff line number Diff line change
Expand Up @@ -777,6 +777,8 @@ class ShellRoute extends ShellRouteBase {
/// * [Custom StatefulShellRoute example](https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/others/custom_stateful_shell_route.dart)
/// which demonstrates how to customize the container for the branch Navigators
/// and how to implement animated transitions when switching branches.
///
/// {@category Configuration}
class StatefulShellRoute extends ShellRouteBase {
/// Constructs a [StatefulShellRoute] from a list of [StatefulShellBranch]es,
/// each representing a separate nested navigation tree (branch).
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.2.1
version: 14.2.2
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

0 comments on commit 9cc06ac

Please sign in to comment.