Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[go_router] Add support for the TabView for more beautiful animation #112267

Open
jopmiddelkamp opened this issue Sep 23, 2022 · 8 comments
Open
Labels
a: animation Animation APIs c: new feature Nothing broken; request for a new capability c: proposal A detailed proposal for a change to Flutter p: go_router The go_router package P3 Issues that are less important to the Flutter project package flutter/packages repository. See also p: labels. team-go_router Owned by Go Router team triaged-go_router Triaged by Go Router team

Comments

@jopmiddelkamp
Copy link

jopmiddelkamp commented Sep 23, 2022

Use case

In our app we are using a TabBar with TabView. This create a nice animation when using the tabs.

ezgif-4-6d09826040

Now with go_router 5.0.0 we can use a ShellRoute but it returns the child in a Navigator. This works but we are losing the nice animation when switching tabs with a TabView.

I was thinking maybe we can create a TabBarShellRoute which gets wrapped in a DefaultTabController and then returns a child as a TabView with all the child routes inside of which each have their own Navigator.

Then maybe create a TabViewGoRoute which doesn't have a pageBuilder and of which the child routes are a list of the normal GoRoute objects.

Proposal

TabBarShellRoute(
  navigatorKey: _homeTabBarShellNavigatorKey,
  builder: (context, state, child, controller) => HomeTabShell(
    controller: controller,
    child: child,
  ),
  routes: [
    TabViewGoRoute(
      name: TransactionsTab.name,
      path: '/transactions',
      builder: (context, state) => const TransactionsTab(),
    ),
    TabViewGoRoute(
      name: SavingsBalancesTab.name,
      path: '/savings-balances',
      builder: (context, state) => const SavingsBalancesTab(),
      routes: [
        GoRoute(
          name: OpenSavingsPage.name,
          path: 'open-new',
          parentNavigatorKey: _rootNavigatorKey,
          builder: (context, state) => const OpenSavingsPage(),
        ),
      ],
    ),
  ],
),
@darshankawar darshankawar added in triage Presently being triaged by the triage team c: new feature Nothing broken; request for a new capability a: animation Animation APIs p: first party package flutter/packages repository. See also p: labels. c: proposal A detailed proposal for a change to Flutter p: go_router The go_router package and removed in triage Presently being triaged by the triage team labels Sep 23, 2022
@tolo
Copy link

tolo commented Sep 27, 2022

Perhaps this can also be solved with a solution like the one I proposed in a comment to #99124. That change proposes a new ShellRoute subclass, that doesn't create a navigator, but instead delegates navigation handling to the child of the shell route.

I was also able to create a workaround without modifying go_router itself - see example in this gist. In that example, a custom ShellRoute subclass is also created, which simply bypasses the navigator of the shell route. Perhaps this example could also be adapted to your use case.

@stuartmorgan stuartmorgan changed the title Add support for the TabView for more beautiful animation [go_router] Add support for the TabView for more beautiful animation Sep 27, 2022
@stuartmorgan stuartmorgan added the P3 Issues that are less important to the Flutter project label Sep 27, 2022
@chunhtai
Copy link
Contributor

This feature can be added once flutter/packages#2650 is landed

@subzero911
Copy link

subzero911 commented Dec 28, 2022

+1 for TabView support. You may want to also support PageView (in case we don't have tabs and want to scroll pages by gesture).
Both TabView and PageView are already supported in auto_route https://pub.dev/packages/auto_route#tab-navigation, but in ugly manner (it requires its own sub-router, so routing is smeared over the application). With ShellRoute it would be much nicer.

@vlkonoshenko
Copy link
Contributor

Some have solution for this issue? Instead of auto_route package

@subzero911
Copy link

Some have solution for this issue? Instead of auto_route package

Unfortunately, go_router is still far behind auto_route.

JeroenWeener pushed a commit to JeroenWeener/flutter-packages that referenced this issue May 22, 2023
Added functionality for building route configuration with support for preserving state in nested navigators. This change introduces a new shell route class called `StatefulShellRoute`, that uses separate navigators for its child routes as well as preserving state in each navigation branch. This is convenient when for instance implementing a UI with a `BottomNavigationBar`, with a persistent navigation state for each tab (i.e. building a `Navigator` for each tab). 
An example showcasing a UI with BottomNavigationBar and StatefulShellRoute has also been added ([`stateful_shell_route.dart`](https://github.com/tolo/flutter_packages/blob/nested-persistent-navigation/packages/go_router/example/lib/stateful_shell_route.dart)). 

Other examples of using `StatefulShellRoute` are also available in these repositories: 
* [stateful_books](https://github.com/tolo/stateful_books) - A fork of the Books example of go_router.
* [stateful_navbar](https://github.com/tolo/stateful_navbar) - A clone of the Flutter Material 3 Navigation Bar example.

<br/>
Below is a short example of how a `StatefulShellRoute` can be setup:

```dart
StatefulShellRoute(
  /// Each separate stateful navigation tree (i.e. Navigator) is represented by
  /// a StatefulShellBranch, which defines the routes that will be placed on that
  /// Navigator. StatefulShellBranch also makes it possible to configure
  /// things like an (optional) Navigator key, the default location (i.e. the
  /// location the branch will be navigated to when loading it for the first time) etc.
  branches: <StatefulShellBranch>[
    StatefulShellBranch(navigatorKey: optionalNavigatorKey, routes: <RouteBase>[
      GoRoute(
        path: '/a',
        builder: (BuildContext context, GoRouterState state) =>
        const RootScreen(label: 'A'),
        routes: <RouteBase>[
          GoRoute(
            path: 'details',
            builder: (BuildContext context, GoRouterState state) =>
            const DetailsScreen(label: 'A'),
          ),
        ],
      ),
    ]),
    /// The default location of a branch will by default be the first of the
    /// configured routes. To configure a different route, provide the
    /// defaultLocation parameter.
    StatefulShellBranch(defaultLocation: '/b/detail', routes: <RouteBase>[
      GoRoute(
        path: '/b',
        builder: (BuildContext context, GoRouterState state) =>
        const RootScreen(label: 'B'),
        routes: <RouteBase>[
          GoRoute(
            path: 'details',
            builder: (BuildContext context, GoRouterState state) =>
            const DetailsScreen(label: 'B'),
          ),
        ],
      ),
    ]),
  ],
  /// Like ShellRoute, the builder builds the navigation shell around the
  /// sub-routes, but with StatefulShellRoute, this navigation shell is able to
  /// maintain the state of the Navigators for each branch. The navigation shell
  /// could for instance use a BottomNavigationBar or similar.
  builder: (BuildContext context, StatefulShellRouteState state, Widget child) =>
      ScaffoldWithNavBar(shellState: state, body: child),
)
```

This fixes issue flutter/flutter#99124.
It also (at least partially) addresses flutter/flutter#112267.
JeroenWeener pushed a commit to JeroenWeener/flutter-packages that referenced this issue May 22, 2023
Added functionality for building route configuration with support for preserving state in nested navigators. This change introduces a new shell route class called `StatefulShellRoute`, that uses separate navigators for its child routes as well as preserving state in each navigation branch. This is convenient when for instance implementing a UI with a `BottomNavigationBar`, with a persistent navigation state for each tab (i.e. building a `Navigator` for each tab). 
An example showcasing a UI with BottomNavigationBar and StatefulShellRoute has also been added ([`stateful_shell_route.dart`](https://github.com/tolo/flutter_packages/blob/nested-persistent-navigation/packages/go_router/example/lib/stateful_shell_route.dart)). 

Other examples of using `StatefulShellRoute` are also available in these repositories: 
* [stateful_books](https://github.com/tolo/stateful_books) - A fork of the Books example of go_router.
* [stateful_navbar](https://github.com/tolo/stateful_navbar) - A clone of the Flutter Material 3 Navigation Bar example.

<br/>
Below is a short example of how a `StatefulShellRoute` can be setup:

```dart
StatefulShellRoute(
  /// Each separate stateful navigation tree (i.e. Navigator) is represented by
  /// a StatefulShellBranch, which defines the routes that will be placed on that
  /// Navigator. StatefulShellBranch also makes it possible to configure
  /// things like an (optional) Navigator key, the default location (i.e. the
  /// location the branch will be navigated to when loading it for the first time) etc.
  branches: <StatefulShellBranch>[
    StatefulShellBranch(navigatorKey: optionalNavigatorKey, routes: <RouteBase>[
      GoRoute(
        path: '/a',
        builder: (BuildContext context, GoRouterState state) =>
        const RootScreen(label: 'A'),
        routes: <RouteBase>[
          GoRoute(
            path: 'details',
            builder: (BuildContext context, GoRouterState state) =>
            const DetailsScreen(label: 'A'),
          ),
        ],
      ),
    ]),
    /// The default location of a branch will by default be the first of the
    /// configured routes. To configure a different route, provide the
    /// defaultLocation parameter.
    StatefulShellBranch(defaultLocation: '/b/detail', routes: <RouteBase>[
      GoRoute(
        path: '/b',
        builder: (BuildContext context, GoRouterState state) =>
        const RootScreen(label: 'B'),
        routes: <RouteBase>[
          GoRoute(
            path: 'details',
            builder: (BuildContext context, GoRouterState state) =>
            const DetailsScreen(label: 'B'),
          ),
        ],
      ),
    ]),
  ],
  /// Like ShellRoute, the builder builds the navigation shell around the
  /// sub-routes, but with StatefulShellRoute, this navigation shell is able to
  /// maintain the state of the Navigators for each branch. The navigation shell
  /// could for instance use a BottomNavigationBar or similar.
  builder: (BuildContext context, StatefulShellRouteState state, Widget child) =>
      ScaffoldWithNavBar(shellState: state, body: child),
)
```

This fixes issue flutter/flutter#99124.
It also (at least partially) addresses flutter/flutter#112267.
JeroenWeener pushed a commit to JeroenWeener/flutter-packages that referenced this issue May 22, 2023
Added functionality for building route configuration with support for preserving state in nested navigators. This change introduces a new shell route class called `StatefulShellRoute`, that uses separate navigators for its child routes as well as preserving state in each navigation branch. This is convenient when for instance implementing a UI with a `BottomNavigationBar`, with a persistent navigation state for each tab (i.e. building a `Navigator` for each tab). 
An example showcasing a UI with BottomNavigationBar and StatefulShellRoute has also been added ([`stateful_shell_route.dart`](https://github.com/tolo/flutter_packages/blob/nested-persistent-navigation/packages/go_router/example/lib/stateful_shell_route.dart)). 

Other examples of using `StatefulShellRoute` are also available in these repositories: 
* [stateful_books](https://github.com/tolo/stateful_books) - A fork of the Books example of go_router.
* [stateful_navbar](https://github.com/tolo/stateful_navbar) - A clone of the Flutter Material 3 Navigation Bar example.

<br/>
Below is a short example of how a `StatefulShellRoute` can be setup:

```dart
StatefulShellRoute(
  /// Each separate stateful navigation tree (i.e. Navigator) is represented by
  /// a StatefulShellBranch, which defines the routes that will be placed on that
  /// Navigator. StatefulShellBranch also makes it possible to configure
  /// things like an (optional) Navigator key, the default location (i.e. the
  /// location the branch will be navigated to when loading it for the first time) etc.
  branches: <StatefulShellBranch>[
    StatefulShellBranch(navigatorKey: optionalNavigatorKey, routes: <RouteBase>[
      GoRoute(
        path: '/a',
        builder: (BuildContext context, GoRouterState state) =>
        const RootScreen(label: 'A'),
        routes: <RouteBase>[
          GoRoute(
            path: 'details',
            builder: (BuildContext context, GoRouterState state) =>
            const DetailsScreen(label: 'A'),
          ),
        ],
      ),
    ]),
    /// The default location of a branch will by default be the first of the
    /// configured routes. To configure a different route, provide the
    /// defaultLocation parameter.
    StatefulShellBranch(defaultLocation: '/b/detail', routes: <RouteBase>[
      GoRoute(
        path: '/b',
        builder: (BuildContext context, GoRouterState state) =>
        const RootScreen(label: 'B'),
        routes: <RouteBase>[
          GoRoute(
            path: 'details',
            builder: (BuildContext context, GoRouterState state) =>
            const DetailsScreen(label: 'B'),
          ),
        ],
      ),
    ]),
  ],
  /// Like ShellRoute, the builder builds the navigation shell around the
  /// sub-routes, but with StatefulShellRoute, this navigation shell is able to
  /// maintain the state of the Navigators for each branch. The navigation shell
  /// could for instance use a BottomNavigationBar or similar.
  builder: (BuildContext context, StatefulShellRouteState state, Widget child) =>
      ScaffoldWithNavBar(shellState: state, body: child),
)
```

This fixes issue flutter/flutter#99124.
It also (at least partially) addresses flutter/flutter#112267.
johnpryan pushed a commit to tolo/flutter_packages that referenced this issue May 22, 2023
Added functionality for building route configuration with support for preserving state in nested navigators. This change introduces a new shell route class called `StatefulShellRoute`, that uses separate navigators for its child routes as well as preserving state in each navigation branch. This is convenient when for instance implementing a UI with a `BottomNavigationBar`, with a persistent navigation state for each tab (i.e. building a `Navigator` for each tab). 
An example showcasing a UI with BottomNavigationBar and StatefulShellRoute has also been added ([`stateful_shell_route.dart`](https://github.com/tolo/flutter_packages/blob/nested-persistent-navigation/packages/go_router/example/lib/stateful_shell_route.dart)). 

Other examples of using `StatefulShellRoute` are also available in these repositories: 
* [stateful_books](https://github.com/tolo/stateful_books) - A fork of the Books example of go_router.
* [stateful_navbar](https://github.com/tolo/stateful_navbar) - A clone of the Flutter Material 3 Navigation Bar example.

<br/>
Below is a short example of how a `StatefulShellRoute` can be setup:

```dart
StatefulShellRoute(
  /// Each separate stateful navigation tree (i.e. Navigator) is represented by
  /// a StatefulShellBranch, which defines the routes that will be placed on that
  /// Navigator. StatefulShellBranch also makes it possible to configure
  /// things like an (optional) Navigator key, the default location (i.e. the
  /// location the branch will be navigated to when loading it for the first time) etc.
  branches: <StatefulShellBranch>[
    StatefulShellBranch(navigatorKey: optionalNavigatorKey, routes: <RouteBase>[
      GoRoute(
        path: '/a',
        builder: (BuildContext context, GoRouterState state) =>
        const RootScreen(label: 'A'),
        routes: <RouteBase>[
          GoRoute(
            path: 'details',
            builder: (BuildContext context, GoRouterState state) =>
            const DetailsScreen(label: 'A'),
          ),
        ],
      ),
    ]),
    /// The default location of a branch will by default be the first of the
    /// configured routes. To configure a different route, provide the
    /// defaultLocation parameter.
    StatefulShellBranch(defaultLocation: '/b/detail', routes: <RouteBase>[
      GoRoute(
        path: '/b',
        builder: (BuildContext context, GoRouterState state) =>
        const RootScreen(label: 'B'),
        routes: <RouteBase>[
          GoRoute(
            path: 'details',
            builder: (BuildContext context, GoRouterState state) =>
            const DetailsScreen(label: 'B'),
          ),
        ],
      ),
    ]),
  ],
  /// Like ShellRoute, the builder builds the navigation shell around the
  /// sub-routes, but with StatefulShellRoute, this navigation shell is able to
  /// maintain the state of the Navigators for each branch. The navigation shell
  /// could for instance use a BottomNavigationBar or similar.
  builder: (BuildContext context, StatefulShellRouteState state, Widget child) =>
      ScaffoldWithNavBar(shellState: state, body: child),
)
```

This fixes issue flutter/flutter#99124.
It also (at least partially) addresses flutter/flutter#112267.
johnpryan pushed a commit to tolo/flutter_packages that referenced this issue May 22, 2023
Added functionality for building route configuration with support for preserving state in nested navigators. This change introduces a new shell route class called `StatefulShellRoute`, that uses separate navigators for its child routes as well as preserving state in each navigation branch. This is convenient when for instance implementing a UI with a `BottomNavigationBar`, with a persistent navigation state for each tab (i.e. building a `Navigator` for each tab). 
An example showcasing a UI with BottomNavigationBar and StatefulShellRoute has also been added ([`stateful_shell_route.dart`](https://github.com/tolo/flutter_packages/blob/nested-persistent-navigation/packages/go_router/example/lib/stateful_shell_route.dart)). 

Other examples of using `StatefulShellRoute` are also available in these repositories: 
* [stateful_books](https://github.com/tolo/stateful_books) - A fork of the Books example of go_router.
* [stateful_navbar](https://github.com/tolo/stateful_navbar) - A clone of the Flutter Material 3 Navigation Bar example.

<br/>
Below is a short example of how a `StatefulShellRoute` can be setup:

```dart
StatefulShellRoute(
  /// Each separate stateful navigation tree (i.e. Navigator) is represented by
  /// a StatefulShellBranch, which defines the routes that will be placed on that
  /// Navigator. StatefulShellBranch also makes it possible to configure
  /// things like an (optional) Navigator key, the default location (i.e. the
  /// location the branch will be navigated to when loading it for the first time) etc.
  branches: <StatefulShellBranch>[
    StatefulShellBranch(navigatorKey: optionalNavigatorKey, routes: <RouteBase>[
      GoRoute(
        path: '/a',
        builder: (BuildContext context, GoRouterState state) =>
        const RootScreen(label: 'A'),
        routes: <RouteBase>[
          GoRoute(
            path: 'details',
            builder: (BuildContext context, GoRouterState state) =>
            const DetailsScreen(label: 'A'),
          ),
        ],
      ),
    ]),
    /// The default location of a branch will by default be the first of the
    /// configured routes. To configure a different route, provide the
    /// defaultLocation parameter.
    StatefulShellBranch(defaultLocation: '/b/detail', routes: <RouteBase>[
      GoRoute(
        path: '/b',
        builder: (BuildContext context, GoRouterState state) =>
        const RootScreen(label: 'B'),
        routes: <RouteBase>[
          GoRoute(
            path: 'details',
            builder: (BuildContext context, GoRouterState state) =>
            const DetailsScreen(label: 'B'),
          ),
        ],
      ),
    ]),
  ],
  /// Like ShellRoute, the builder builds the navigation shell around the
  /// sub-routes, but with StatefulShellRoute, this navigation shell is able to
  /// maintain the state of the Navigators for each branch. The navigation shell
  /// could for instance use a BottomNavigationBar or similar.
  builder: (BuildContext context, StatefulShellRouteState state, Widget child) =>
      ScaffoldWithNavBar(shellState: state, body: child),
)
```

This fixes issue flutter/flutter#99124.
It also (at least partially) addresses flutter/flutter#112267.
johnpryan pushed a commit to tolo/flutter_packages that referenced this issue May 22, 2023
Added functionality for building route configuration with support for preserving state in nested navigators. This change introduces a new shell route class called `StatefulShellRoute`, that uses separate navigators for its child routes as well as preserving state in each navigation branch. This is convenient when for instance implementing a UI with a `BottomNavigationBar`, with a persistent navigation state for each tab (i.e. building a `Navigator` for each tab). 
An example showcasing a UI with BottomNavigationBar and StatefulShellRoute has also been added ([`stateful_shell_route.dart`](https://github.com/tolo/flutter_packages/blob/nested-persistent-navigation/packages/go_router/example/lib/stateful_shell_route.dart)). 

Other examples of using `StatefulShellRoute` are also available in these repositories: 
* [stateful_books](https://github.com/tolo/stateful_books) - A fork of the Books example of go_router.
* [stateful_navbar](https://github.com/tolo/stateful_navbar) - A clone of the Flutter Material 3 Navigation Bar example.

<br/>
Below is a short example of how a `StatefulShellRoute` can be setup:

```dart
StatefulShellRoute(
  /// Each separate stateful navigation tree (i.e. Navigator) is represented by
  /// a StatefulShellBranch, which defines the routes that will be placed on that
  /// Navigator. StatefulShellBranch also makes it possible to configure
  /// things like an (optional) Navigator key, the default location (i.e. the
  /// location the branch will be navigated to when loading it for the first time) etc.
  branches: <StatefulShellBranch>[
    StatefulShellBranch(navigatorKey: optionalNavigatorKey, routes: <RouteBase>[
      GoRoute(
        path: '/a',
        builder: (BuildContext context, GoRouterState state) =>
        const RootScreen(label: 'A'),
        routes: <RouteBase>[
          GoRoute(
            path: 'details',
            builder: (BuildContext context, GoRouterState state) =>
            const DetailsScreen(label: 'A'),
          ),
        ],
      ),
    ]),
    /// The default location of a branch will by default be the first of the
    /// configured routes. To configure a different route, provide the
    /// defaultLocation parameter.
    StatefulShellBranch(defaultLocation: '/b/detail', routes: <RouteBase>[
      GoRoute(
        path: '/b',
        builder: (BuildContext context, GoRouterState state) =>
        const RootScreen(label: 'B'),
        routes: <RouteBase>[
          GoRoute(
            path: 'details',
            builder: (BuildContext context, GoRouterState state) =>
            const DetailsScreen(label: 'B'),
          ),
        ],
      ),
    ]),
  ],
  /// Like ShellRoute, the builder builds the navigation shell around the
  /// sub-routes, but with StatefulShellRoute, this navigation shell is able to
  /// maintain the state of the Navigators for each branch. The navigation shell
  /// could for instance use a BottomNavigationBar or similar.
  builder: (BuildContext context, StatefulShellRouteState state, Widget child) =>
      ScaffoldWithNavBar(shellState: state, body: child),
)
```

This fixes issue flutter/flutter#99124.
It also (at least partially) addresses flutter/flutter#112267.
@palancana
Copy link

This feature can be added once flutter/packages#2650 is landed

The great flutter/packages#2650 PR has finally been merged! I understand, though, that animations were out of the scope. Are there any hopes for adding TabView or PageView support, so we can take profit of those animations?

Or maybe there is another workaround to use those animations with the current StatefulShellRoute, but I couldn't figure out how. Maybe @tolo or @chunhtai has an opinion there.

@flutter-triage-bot flutter-triage-bot bot added multiteam-retriage-candidate team-go_router Owned by Go Router team triaged-go_router Triaged by Go Router team labels Jul 8, 2023
nploi pushed a commit to nploi/packages that referenced this issue Jul 16, 2023
Added functionality for building route configuration with support for preserving state in nested navigators. This change introduces a new shell route class called `StatefulShellRoute`, that uses separate navigators for its child routes as well as preserving state in each navigation branch. This is convenient when for instance implementing a UI with a `BottomNavigationBar`, with a persistent navigation state for each tab (i.e. building a `Navigator` for each tab). 
An example showcasing a UI with BottomNavigationBar and StatefulShellRoute has also been added ([`stateful_shell_route.dart`](https://github.com/tolo/flutter_packages/blob/nested-persistent-navigation/packages/go_router/example/lib/stateful_shell_route.dart)). 

Other examples of using `StatefulShellRoute` are also available in these repositories: 
* [stateful_books](https://github.com/tolo/stateful_books) - A fork of the Books example of go_router.
* [stateful_navbar](https://github.com/tolo/stateful_navbar) - A clone of the Flutter Material 3 Navigation Bar example.

<br/>
Below is a short example of how a `StatefulShellRoute` can be setup:

```dart
StatefulShellRoute(
  /// Each separate stateful navigation tree (i.e. Navigator) is represented by
  /// a StatefulShellBranch, which defines the routes that will be placed on that
  /// Navigator. StatefulShellBranch also makes it possible to configure
  /// things like an (optional) Navigator key, the default location (i.e. the
  /// location the branch will be navigated to when loading it for the first time) etc.
  branches: <StatefulShellBranch>[
    StatefulShellBranch(navigatorKey: optionalNavigatorKey, routes: <RouteBase>[
      GoRoute(
        path: '/a',
        builder: (BuildContext context, GoRouterState state) =>
        const RootScreen(label: 'A'),
        routes: <RouteBase>[
          GoRoute(
            path: 'details',
            builder: (BuildContext context, GoRouterState state) =>
            const DetailsScreen(label: 'A'),
          ),
        ],
      ),
    ]),
    /// The default location of a branch will by default be the first of the
    /// configured routes. To configure a different route, provide the
    /// defaultLocation parameter.
    StatefulShellBranch(defaultLocation: '/b/detail', routes: <RouteBase>[
      GoRoute(
        path: '/b',
        builder: (BuildContext context, GoRouterState state) =>
        const RootScreen(label: 'B'),
        routes: <RouteBase>[
          GoRoute(
            path: 'details',
            builder: (BuildContext context, GoRouterState state) =>
            const DetailsScreen(label: 'B'),
          ),
        ],
      ),
    ]),
  ],
  /// Like ShellRoute, the builder builds the navigation shell around the
  /// sub-routes, but with StatefulShellRoute, this navigation shell is able to
  /// maintain the state of the Navigators for each branch. The navigation shell
  /// could for instance use a BottomNavigationBar or similar.
  builder: (BuildContext context, StatefulShellRouteState state, Widget child) =>
      ScaffoldWithNavBar(shellState: state, body: child),
)
```

This fixes issue flutter/flutter#99124.
It also (at least partially) addresses flutter/flutter#112267.
nploi pushed a commit to nploi/packages that referenced this issue Jul 16, 2023
Added functionality for building route configuration with support for preserving state in nested navigators. This change introduces a new shell route class called `StatefulShellRoute`, that uses separate navigators for its child routes as well as preserving state in each navigation branch. This is convenient when for instance implementing a UI with a `BottomNavigationBar`, with a persistent navigation state for each tab (i.e. building a `Navigator` for each tab). 
An example showcasing a UI with BottomNavigationBar and StatefulShellRoute has also been added ([`stateful_shell_route.dart`](https://github.com/tolo/flutter_packages/blob/nested-persistent-navigation/packages/go_router/example/lib/stateful_shell_route.dart)). 

Other examples of using `StatefulShellRoute` are also available in these repositories: 
* [stateful_books](https://github.com/tolo/stateful_books) - A fork of the Books example of go_router.
* [stateful_navbar](https://github.com/tolo/stateful_navbar) - A clone of the Flutter Material 3 Navigation Bar example.

<br/>
Below is a short example of how a `StatefulShellRoute` can be setup:

```dart
StatefulShellRoute(
  /// Each separate stateful navigation tree (i.e. Navigator) is represented by
  /// a StatefulShellBranch, which defines the routes that will be placed on that
  /// Navigator. StatefulShellBranch also makes it possible to configure
  /// things like an (optional) Navigator key, the default location (i.e. the
  /// location the branch will be navigated to when loading it for the first time) etc.
  branches: <StatefulShellBranch>[
    StatefulShellBranch(navigatorKey: optionalNavigatorKey, routes: <RouteBase>[
      GoRoute(
        path: '/a',
        builder: (BuildContext context, GoRouterState state) =>
        const RootScreen(label: 'A'),
        routes: <RouteBase>[
          GoRoute(
            path: 'details',
            builder: (BuildContext context, GoRouterState state) =>
            const DetailsScreen(label: 'A'),
          ),
        ],
      ),
    ]),
    /// The default location of a branch will by default be the first of the
    /// configured routes. To configure a different route, provide the
    /// defaultLocation parameter.
    StatefulShellBranch(defaultLocation: '/b/detail', routes: <RouteBase>[
      GoRoute(
        path: '/b',
        builder: (BuildContext context, GoRouterState state) =>
        const RootScreen(label: 'B'),
        routes: <RouteBase>[
          GoRoute(
            path: 'details',
            builder: (BuildContext context, GoRouterState state) =>
            const DetailsScreen(label: 'B'),
          ),
        ],
      ),
    ]),
  ],
  /// Like ShellRoute, the builder builds the navigation shell around the
  /// sub-routes, but with StatefulShellRoute, this navigation shell is able to
  /// maintain the state of the Navigators for each branch. The navigation shell
  /// could for instance use a BottomNavigationBar or similar.
  builder: (BuildContext context, StatefulShellRouteState state, Widget child) =>
      ScaffoldWithNavBar(shellState: state, body: child),
)
```

This fixes issue flutter/flutter#99124.
It also (at least partially) addresses flutter/flutter#112267.
nploi pushed a commit to nploi/packages that referenced this issue Jul 16, 2023
Added functionality for building route configuration with support for preserving state in nested navigators. This change introduces a new shell route class called `StatefulShellRoute`, that uses separate navigators for its child routes as well as preserving state in each navigation branch. This is convenient when for instance implementing a UI with a `BottomNavigationBar`, with a persistent navigation state for each tab (i.e. building a `Navigator` for each tab). 
An example showcasing a UI with BottomNavigationBar and StatefulShellRoute has also been added ([`stateful_shell_route.dart`](https://github.com/tolo/flutter_packages/blob/nested-persistent-navigation/packages/go_router/example/lib/stateful_shell_route.dart)). 

Other examples of using `StatefulShellRoute` are also available in these repositories: 
* [stateful_books](https://github.com/tolo/stateful_books) - A fork of the Books example of go_router.
* [stateful_navbar](https://github.com/tolo/stateful_navbar) - A clone of the Flutter Material 3 Navigation Bar example.

<br/>
Below is a short example of how a `StatefulShellRoute` can be setup:

```dart
StatefulShellRoute(
  /// Each separate stateful navigation tree (i.e. Navigator) is represented by
  /// a StatefulShellBranch, which defines the routes that will be placed on that
  /// Navigator. StatefulShellBranch also makes it possible to configure
  /// things like an (optional) Navigator key, the default location (i.e. the
  /// location the branch will be navigated to when loading it for the first time) etc.
  branches: <StatefulShellBranch>[
    StatefulShellBranch(navigatorKey: optionalNavigatorKey, routes: <RouteBase>[
      GoRoute(
        path: '/a',
        builder: (BuildContext context, GoRouterState state) =>
        const RootScreen(label: 'A'),
        routes: <RouteBase>[
          GoRoute(
            path: 'details',
            builder: (BuildContext context, GoRouterState state) =>
            const DetailsScreen(label: 'A'),
          ),
        ],
      ),
    ]),
    /// The default location of a branch will by default be the first of the
    /// configured routes. To configure a different route, provide the
    /// defaultLocation parameter.
    StatefulShellBranch(defaultLocation: '/b/detail', routes: <RouteBase>[
      GoRoute(
        path: '/b',
        builder: (BuildContext context, GoRouterState state) =>
        const RootScreen(label: 'B'),
        routes: <RouteBase>[
          GoRoute(
            path: 'details',
            builder: (BuildContext context, GoRouterState state) =>
            const DetailsScreen(label: 'B'),
          ),
        ],
      ),
    ]),
  ],
  /// Like ShellRoute, the builder builds the navigation shell around the
  /// sub-routes, but with StatefulShellRoute, this navigation shell is able to
  /// maintain the state of the Navigators for each branch. The navigation shell
  /// could for instance use a BottomNavigationBar or similar.
  builder: (BuildContext context, StatefulShellRouteState state, Widget child) =>
      ScaffoldWithNavBar(shellState: state, body: child),
)
```

This fixes issue flutter/flutter#99124.
It also (at least partially) addresses flutter/flutter#112267.
@subzero911
Copy link

You already have a StatefulShellRoute.indexedStack
It now should be easy to implement StatefulShellRoute.pageView. It essentially would be the same, but with a beautiful swipe animation between the screens.
Looking forward for it!

@yanshouwang
Copy link

yanshouwang commented May 25, 2024

There is a CustomStateShellRoute example, we can use PageView instead of the AnimatedBranchContainer

// GoRouter
final routerConfig = GoRouter(
  initialLocation: '/central-manager',
  navigatorKey: _rootNavigatorKey,
  routes: [
    StatefulShellRoute(
      // builder: (context, state, navigationShell) {
      //   return HomeView(navigationShell: navigationShell);
      // },
      builder: (context, state, navigationShell) => navigationShell,
      navigatorContainerBuilder: (context, navigationShell, children) {
        return HomeView(
          navigationShell: navigationShell,
          children: children,
        );
      },
      branches: [
        StatefulShellBranch(
          routes: [
            GoRoute(
              path: '/central-manager',
              builder: (context, state) {
                return const CentralManagerView();
              },
              routes: [
                GoRoute(
                  path: 'peripheral',
                  builder: (context, state) {
                    final eventArgs = state.extra as DiscoveredEventArgs;
                    return PeripheralView(
                      eventArgs: eventArgs,
                    );
                  },
                ),
              ],
            ),
          ],
        ),
        StatefulShellBranch(
          routes: [
            GoRoute(
              path: '/peripheral-manager',
              builder: (context, state) {
                return const PeripheralManagerView();
              },
            ),
          ],
        ),
      ],
    ),
  ],
);
// HomeView
class HomeView extends StatefulWidget {
  final StatefulNavigationShell navigationShell;
  final List<Widget> children;

  const HomeView({
    super.key,
    required this.navigationShell,
    required this.children,
  });

  @override
  State<HomeView> createState() => _HomeViewState();
}

class _HomeViewState extends State<HomeView> {
  late final PageController _controller;

  @override
  void initState() {
    super.initState();
    _controller = PageController(
      initialPage: widget.navigationShell.currentIndex,
    );
  }

  @override
  Widget build(BuildContext context) {
    final navigationShell = widget.navigationShell;
    final children = widget.children;
    return Scaffold(
      // body: navigationShell,
      // body: AnimatedBranchContainer(
      //   currentIndex: widget.navigationShell.currentIndex,
      //   children: widget.children,
      // ),
      body: PageView.builder(
        controller: _controller,
        onPageChanged: (index) {
          debugPrint(
              'index: $index, currentIndex: ${navigationShell.currentIndex}');
          // Ignore tap events.
          if (index == navigationShell.currentIndex) {
            return;
          }
          navigationShell.goBranch(
            index,
            initialLocation: false,
          );
        },
        itemBuilder: (context, index) {
          return children[index];
        },
        itemCount: children.length,
      ),
      bottomNavigationBar: BottomNavigationBar(
        onTap: (index) {
          navigationShell.goBranch(
            index,
            initialLocation: index == navigationShell.currentIndex,
          );
        },
        items: const [
          BottomNavigationBarItem(
            icon: Icon(Icons.radar),
            label: 'Central',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.sensors),
            label: 'Peripheral',
          ),
        ],
        currentIndex: widget.navigationShell.currentIndex,
      ),
    );
  }

  @override
  void didUpdateWidget(covariant HomeView oldWidget) {
    super.didUpdateWidget(oldWidget);
    final navigationShell = widget.navigationShell;
    final page = _controller.page ?? _controller.initialPage;
    final index = page.round();
    debugPrint('index: $index, currentIndex: ${navigationShell.currentIndex}');
    // Ignore swipe events.
    if (index == navigationShell.currentIndex) {
      return;
    }
    _controller.animateToPage(
      widget.navigationShell.currentIndex,
      duration: const Duration(milliseconds: 300),
      curve: Curves.linear,
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a: animation Animation APIs c: new feature Nothing broken; request for a new capability c: proposal A detailed proposal for a change to Flutter p: go_router The go_router package P3 Issues that are less important to the Flutter project package flutter/packages repository. See also p: labels. team-go_router Owned by Go Router team triaged-go_router Triaged by Go Router team
Projects
No open projects
Status: No status
Development

No branches or pull requests

10 participants