From 683ddc4f30164b442169c70cf9beddcd1c1f1e9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Lo=CC=88fstrand?= Date: Tue, 12 Mar 2024 22:08:51 +0100 Subject: [PATCH 1/3] Added section for "Stateful nested navigation" --- packages/go_router/doc/configuration.md | 78 +++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/packages/go_router/doc/configuration.md b/packages/go_router/doc/configuration.md index bb3dba5e6ec5..c21f82976215 100644 --- a/packages/go_router/doc/configuration.md +++ b/packages/go_router/doc/configuration.md @@ -152,6 +152,83 @@ 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: + +```dart +StatefulShellRoute.indexedStack( + branches: [ + StatefulShellBranch( + routes: [ + GoRoute( + path: '/a', + builder: (BuildContext context, GoRouterState state) { + return const RootScreen(label: 'A'); + }, + ), + ], + ), + /* ... */ + ], + /* builder: ... */ +); +``` + +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: + +```dart +StatefulShellRoute.indexedStack( + builder: (BuildContext context, GoRouterState state, + StatefulNavigationShell navigationShell) { + return Scaffold( + body: navigationShell, + /* ... */ + bottomNavigationBar: BottomNavigationBar( + /* ... */ + ), + ); + }, + /* branches: ... */ +) +``` + +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: + +```dart +return Scaffold( + body: navigationShell, + bottomNavigationBar: BottomNavigationBar( + /* items: ... */ + currentIndex: navigationShell.currentIndex, + 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 @@ -181,3 +258,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/ShellRoute-class.html From a42e6633a76a67ed64853dd281ae51a65150cdc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Lo=CC=88fstrand?= Date: Tue, 12 Mar 2024 22:19:57 +0100 Subject: [PATCH 2/3] Updated version and CHANGELOG --- packages/go_router/CHANGELOG.md | 3 ++- packages/go_router/pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/go_router/CHANGELOG.md b/packages/go_router/CHANGELOG.md index 3b01fb1dbb49..a930635cc76f 100644 --- a/packages/go_router/CHANGELOG.md +++ b/packages/go_router/CHANGELOG.md @@ -1,6 +1,7 @@ -## NEXT +## 13.2.1 * Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Adds section for "Stateful nested navigation" to configuration.md. ## 13.2.0 diff --git a/packages/go_router/pubspec.yaml b/packages/go_router/pubspec.yaml index b93509a97adf..7d6ae9be1a2f 100644 --- a/packages/go_router/pubspec.yaml +++ b/packages/go_router/pubspec.yaml @@ -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: 13.2.0 +version: 13.2.1 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 From b6597a423215c2788d787ccde76c0f48c6cd0c33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Lo=CC=88fstrand?= Date: Mon, 29 Apr 2024 16:34:34 +0200 Subject: [PATCH 3/3] Updated code snippets to use code-excerpt --- packages/go_router/doc/configuration.md | 90 ++++++++++++------- .../example/lib/stateful_shell_route.dart | 18 +++- packages/go_router/lib/src/route.dart | 2 + 3 files changed, 73 insertions(+), 37 deletions(-) diff --git a/packages/go_router/doc/configuration.md b/packages/go_router/doc/configuration.md index c21f82976215..6c4f1ec6ee4d 100644 --- a/packages/go_router/doc/configuration.md +++ b/packages/go_router/doc/configuration.md @@ -167,23 +167,32 @@ provides a default implementation for managing the branch navigators, using an When using StatefulShellRoute, routes aren't configured on the shell route itself. Instead, they are configured for each of the branches. Example: + ```dart -StatefulShellRoute.indexedStack( - branches: [ - StatefulShellBranch( - routes: [ - GoRoute( - path: '/a', - builder: (BuildContext context, GoRouterState state) { - return const RootScreen(label: 'A'); - }, - ), - ], - ), - /* ... */ - ], - /* builder: ... */ -); +branches: [ + // The route branch for the first tab of the bottom navigation bar. + StatefulShellBranch( + navigatorKey: _sectionANavigatorKey, + routes: [ + 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: [ + // 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 @@ -191,20 +200,17 @@ 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: + ```dart StatefulShellRoute.indexedStack( builder: (BuildContext context, GoRouterState state, StatefulNavigationShell navigationShell) { - return Scaffold( - body: navigationShell, - /* ... */ - bottomNavigationBar: BottomNavigationBar( - /* ... */ - ), - ); + // 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); }, - /* branches: ... */ -) ``` Within the custom shell widget, the StatefulNavigationShell is first and @@ -212,15 +218,31 @@ 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: + ```dart -return Scaffold( - body: navigationShell, - bottomNavigationBar: BottomNavigationBar( - /* items: ... */ - currentIndex: navigationShell.currentIndex, - onTap: (int index) => navigationShell.goBranch(index), - ), -); +@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(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 @@ -258,4 +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/ShellRoute-class.html +[StatefulShellRoute]: https://pub.dev/documentation/go_router/latest/go_router/StatefulShellRoute-class.html diff --git a/packages/go_router/example/lib/stateful_shell_route.dart b/packages/go_router/example/lib/stateful_shell_route.dart index 9901619d7ce9..e6f0e7a1c0c6 100644 --- a/packages/go_router/example/lib/stateful_shell_route.dart +++ b/packages/go_router/example/lib/stateful_shell_route.dart @@ -28,6 +28,7 @@ class NestedTabNavigationExampleApp extends StatelessWidget { navigatorKey: _rootNavigatorKey, initialLocation: '/a', routes: [ + // #docregion configuration-builder StatefulShellRoute.indexedStack( builder: (BuildContext context, GoRouterState state, StatefulNavigationShell navigationShell) { @@ -37,6 +38,8 @@ class NestedTabNavigationExampleApp extends StatelessWidget { // branches in a stateful way. return ScaffoldWithNavBar(navigationShell: navigationShell); }, + // #enddocregion configuration-builder + // #docregion configuration-branches branches: [ // The route branch for the first tab of the bottom navigation bar. StatefulShellBranch( @@ -61,6 +64,7 @@ class NestedTabNavigationExampleApp extends StatelessWidget { ), ], ), + // #enddocregion configuration-branches // The route branch for the second tab of the bottom navigation bar. StatefulShellBranch( @@ -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 @@ -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 diff --git a/packages/go_router/lib/src/route.dart b/packages/go_router/lib/src/route.dart index 69dd904f087d..c680f90d4861 100644 --- a/packages/go_router/lib/src/route.dart +++ b/packages/go_router/lib/src/route.dart @@ -773,6 +773,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).