Skip to content

Instantly share code, notes, and snippets.

@tolo
Last active March 23, 2024 07:49
Show Gist options
  • Save tolo/f7e6c30cad3ac76085d75255ba509f10 to your computer and use it in GitHub Desktop.
Save tolo/f7e6c30cad3ac76085d75255ba509f10 to your computer and use it in GitHub Desktop.
Example showing how to use go_router to build persistent nested navigation (i.e. separate nested navigation trees) with a BottomNavigationBar.
// This temporary implementation is now obsolete, see instead:
// https://pub.dev/documentation/go_router/latest/go_router/StatefulShellRoute-class.html
@esDotDev
Copy link

esDotDev commented Oct 21, 2022

I'm confused how this actually works. Wouldn't the root navigator only hold state for the current stack of routes?

I understand that you store root navigator it in Offstage widget, and proxy .pages to the child navigator, just not getting how state is actually preserved in the non-active pages.

Are things like scroll position, textfield contents, or view-level sorting options preserved when changing between different tabs?

@tolo
Copy link
Author

tolo commented Oct 21, 2022

In this example, the root navigator will only contain a single Page, i.e. the one for BottomTabBarShellRoute. The Navigator for each tab will then hold the navigation stack for a particular tab, and since every Navigator will be kept "alive" in the IndexedStack (although Offstage), widgets in the navigation stacks will not be disposed when switching tabs.

So yes, everything will be as you left it, when returning to a previous tab.

@esDotDev
Copy link

I get it, super cool and elegant, very nice work!

@esDotDev
Copy link

esDotDev commented Oct 21, 2022

One potential I see here is that the scaffold has no access to a Navigator (and thus Overlay).

Can you show dialogs, or tooltips etc from within your scaffold with this technique? Or even just do Overlay.of(context) at all?

@tolo
Copy link
Author

tolo commented Oct 21, 2022

I get it, super cool and elegant, very nice work!

Thanks! 😊

Can you show dialogs, or tooltips etc from within your scaffold with this technique? Or even just do Overlay.of(context) at all?'

Should be fine. I just tried it (dialog and snackbar) in the sample code part of flutter/packages#2652. Perhaps I'll leave it in that sample.

@esDotDev
Copy link

esDotDev commented Oct 21, 2022

Weird... so that must mean that despite the root Navigator being offstage, it's internal Overlay is still accessible? INTERESTING!

@tolo
Copy link
Author

tolo commented Oct 21, 2022

No, the root navigator is never offstage, only the sibling navigators in the IndexStack used for the bottom navigation. That is, at any point in time, there will only be one nested navigator “onstage”, and the parent (root) navigator will remain onstage at all times.

@tolo
Copy link
Author

tolo commented Oct 22, 2022

Ok, this line threw me off: https://gist.github.com/tolo/f7e6c30cad3ac76085d75255ba509f10#file-nested_navigation_shell_route-dart-L126

Right, yeah, that's the ugly bit with this workaround, which you won't get with flutter/packages#2650. But that Navigator is anyway the one created by ShellRoute, not the root one. So it's just hiding an obsolete Navigator.

@amerblackbird
Copy link

Offstage widget cause main screen build twice.

@hmbenhaim
Copy link

hmbenhaim commented Nov 22, 2022

Offstage widget cause main screen build twice.

It's amazing just i have 1 issue.
just burn a few hours till I found it. when the keyboard gets visible offstage gets called and then rebuild the all page, again and again, so it's impossible to use it with text field on android, on desktop it fine but still rebuilds many times

@davidhicks980
Copy link

Excellent fix @tolo !

One small addition. If you are having trouble with hero animations, I'm fairly certain you can add a HeroController() observer to your nested Navigator. From what I can tell from the docs, MaterialApp usually adds this automatically.

 final heroController = HeroController();

  Widget buildNavigator(BuildContext context) {
    if (pages.isNotEmpty) {
      return Navigator(
        observers: [heroController],
        key: navigatorKey,
        pages: pages,
        onPopPage: _handlePopPage,
      );
    } else {
      return const SizedBox.shrink();
    }
  }

@tolo
Copy link
Author

tolo commented Dec 4, 2022

@davidhicks980, actually support for navigation observers (and hero controllers) is being implemented in the PR flutter/packages#2664. Hoping that will be finished soon so flutter/packages#2650 can benefit from it.

My main focus right not is to complete flutter/packages#2650 so it can be merged into go_router. @amerblackbird and @hmbenhaim, please have a look at that branch and the corresponding sample (stateful_shell_route.dart) there, and see if that works better. If not, please add a comment in flutter/packages#2650.

@hmbenhaim
Copy link

hmbenhaim commented Dec 4, 2022

@amerblackbird and @hmbenhaim, please have a look at that branch and the corresponding sample (stateful_shell_route.dart) there, and see if that works better. If not, please add a comment in flutter/packages#2650.

Hi @tolo I have tried this branch and this sample it works amazing so simple and so good and perform very well. I hadn't any issues with it so far I cloned this branch and I'm using it directly once it merge I'll use the library again. Waiting for the merge. Thank you!

@tolo
Copy link
Author

tolo commented Dec 5, 2022

Happy to hear @hmbenhaim! 😊

@sv-22
Copy link

sv-22 commented Jan 29, 2023

Mate, you are a legend! Thanks a lot! 🍻

@tolo
Copy link
Author

tolo commented Jan 29, 2023

Mate, you are a legend! Thanks a lot! 🍻

🙏😊🍻

@hazzo
Copy link

hazzo commented Feb 26, 2023

Here are my 2 cents until the PR adding support for this use case it's merged. If you updated to go_router 6.0.7+ this was introduced:

Use HeroControllerScope for nested Navigator that fixes Hero Widgets not animating in Nested Navigator.

So the above gist won't work until a small change it's made to work with 6.0.7 versions and above.
On the ScaffoldWithNavBar widget inside the BottomTabBarShellRoute the currentNavigator prop can't be casted to Navigator directly because the builder method will not return now a Navigatorbut instead a HeroControllerScope.

So in line 128 this change should be done to make the gist work:

ScaffoldWithNavBar(tabs: tabs, key: scaffoldKey,
  currentNavigator: (fauxNav as HeroControllerScope).child as Navigator,
  currentRouterState: state, routes: routes),
]);

@tanhsnkt1997
Copy link

why in StatefulWidget i call @OverRide
void initState() {
super.initState();
print("-----RUN------ ")}

  • At tab:: firstly -----RUN------ call double, but since the 2nd time onwards it only calls 1 time. Please help me :(

@Zeehshan
Copy link

Here are my 2 cents until the PR adding support for this use case it's merged. If you updated to go_router 6.0.7+ this was introduced:

Use HeroControllerScope for nested Navigator that fixes Hero Widgets not animating in Nested Navigator.

So the above gist won't work until a small change it's made to work with 6.0.7 versions and above. On the ScaffoldWithNavBar widget inside the BottomTabBarShellRoute the currentNavigator prop can't be casted to Navigator directly because the builder method will not return now a Navigatorbut instead a HeroControllerScope.

So in line 128 this change should be done to make the gist work:

ScaffoldWithNavBar(tabs: tabs, key: scaffoldKey,
  currentNavigator: (fauxNav as HeroControllerScope).child as Navigator,
  currentRouterState: state, routes: routes),
]);

type '_CustomNavigator' is not a subtype of type 'HeroControllerScope' in type cast

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment