Skip to content
This repository has been archived by the owner on Feb 25, 2022. It is now read-only.

provide for multiple navigation stacks? #82

Closed
Tracked by #174
csells opened this issue Oct 9, 2021 · 60 comments
Closed
Tracked by #174

provide for multiple navigation stacks? #82

csells opened this issue Oct 9, 2021 · 60 comments
Labels
enhancement New feature or request

Comments

@csells
Copy link
Owner

csells commented Oct 9, 2021

e.g. https://medium.com/coding-with-flutter/flutter-case-study-multiple-navigators-with-bottomnavigationbar-90eb6caa6dbf

@csells csells added the enhancement New feature or request label Oct 9, 2021
@rydmike
Copy link
Contributor

rydmike commented Oct 13, 2021

The iOS style nested state keeping bottom navigation pattern is one that is often asked for when talking about nested navigation.

I wonder if it can't be built already with current version and some tiny bit of extra Flutter code. I might try it later.

@csells
Copy link
Owner Author

csells commented Oct 13, 2021

That would be great if you could give it a shot @rydmike . It's become the #1 outstanding issue.

@bizz84
Copy link

bizz84 commented Oct 15, 2021

Happy to see this on the roadmap. I think it would be a great addition.

My approach uses Navigator 1.0 and has some drawbacks as it keeps all the navigation stacks in memory.

Potentially it would be better if a Navigator 2.0-based solution could keep track of the state of each page/stack, and recreate the pages as needed when the user switches between tabs.

@rydmike
Copy link
Contributor

rydmike commented Oct 15, 2021

If you want to check out how the "competition" is doing this, then Routemaster supports it. But it is based on Cupertino solution in the SDK and limited to it.

I wanted to have this feature also on other "custom" top level navigation widgets like sidebars, top navs in web, nav rails (SDK and custom variant) etc.

When experimenting with Routemaster a few versions back, I "borrowed" the stack solution from Flutter SDK Cupertino code, and used it as a stack for any indexed custom navigator widget to create the same multiple navigation stacks solution.

I think it can be used as is with GoRouter as well, but I have not tried the idea yet, been to busy with other things, but very curios to test it, ...eventually, hopefully soon. When I do, I will use a suitable package sample to try the idea.

@ChauCM
Copy link

ChauCM commented Oct 15, 2021

If you want to check out how the "competition" is doing this, then Routemaster supports it. But it is based on Cupertino solution in the SDK and limited to it.

I wanted to have this feature also on other "custom" top level navigation widgets like sidebars, top navs in web, nav rails (SDK and custom variant) etc.

When experimenting with Routemaster a few versions back, I "borrowed" the stack solution from Flutter SDK Cupertino code, and used it as a stack for any indexed custom navigator widget to create the same multiple navigation stacks solution.

I think it can be used as is with GoRouter as well, but I have not tried the idea yet, been to busy with other things, but very curios to test it, ...eventually, hopefully soon. When I do, I will use a suitable package sample to try the idea.

Beamer also supports it, they have an example in here: https://github.com/slovnicki/beamer/tree/master/examples/bottom_navigation_multiple_beamers

@csells
Copy link
Owner Author

csells commented Oct 15, 2021

Thanks, @rydmike . I'm looking forward to what you come up with.

@KristianBalaj
Copy link

KristianBalaj commented Oct 19, 2021

I'm focusing on this feature (and on the nested navigation in general) in my package routeborn.

When there is a nested navigation, the navigation stack is branching into multiple navigation stacks like a tree. Currently the implementation is in a way of single source of truth regarding the navigation stacks (the SSOT is the NavigationNotifier in my package). The NavigationNotifier is managing all the stacks.

Since the branching analogy, a method for changing branches in nested navigation is the setNestingBranch method, where by default the navigation stacks are preserved.
In case a user doesn't want the stack preservance feature, there is an optional parameter bool resetBranchStack.

EDIT:
Also, this example is using the CupertinoTabScaffold, where for each branch a separate Router is used and the stacks are preserved.
But the routeborn package can be used with a single Router covering all the branches, too.

@csells
Copy link
Owner Author

csells commented Oct 19, 2021

Hey @KristianBalaj would you want to join forces and bring your multiple navigation stack expertise to go_router?

@KristianBalaj
Copy link

That would be nice, but currently I don't have resources to contribute to open source.
So it's more optimal for me to maintain a separate package for now, since I'm using this package on 2 projects under development.

@samdogg7
Copy link

I would love to see this feature! This is the one thing holding me back from using go_router. Thanks @csells for all of your amazing work so far.

@esDotDev
Copy link

esDotDev commented Oct 30, 2021

I think go_router does everything it can do to implement this feature, it can't really be implemented "out of the box", the flutter developer needs to do a little bit of extra lifting.

This is because go_router is path based, there is fundamentally no such thing as 'multiple stacks' ever in memory. There is simply a path, and that will match to some stack of pages, and you can wrap those pages in an external tab menu, which gets you most of the way to nested nav in appearence, just not in behavior.

What's left to do, is the key feature people expect from this paradigm which is "each tab preserves it's state". This breaks down into 2 parts as far as I see:

  • Restore the correct page when switching to a tab (it should load the page it was showing previously, and remember the viewstack)
  • Have the page itself remember it's state (scroll position, selected sort options, etc)

The first thing can be done by having some imperative code that saves off the last viewed path for each tab, and when the tab is pressed, it tries to look up the last known page, and show that rather than the default. For example, the default url for tab1 might be /tab1/messages but it could link instead /tab1/messages/compose if that was the last recorded path for tab1. Given that later path, GoRouter would be smart enough to stack the Compose page on top of Messages and pop() would work as expected.

The second requirement is harder, having the stateful views remember/restore their state. I don't think there is actually a good solution for this that will work with GoRouter, unless you just have 1 top level pageRoute and all subsequent page routing is handled internally inside that widget. In theory the Restoration API could be extended to serve this use case I think. In the meantime it would probably have to be done via some form of persistent storage and having views manually restore their state when loaded, using an id (just like Restoration API does).

@davidmartos96
Copy link

I think go_router does everything it can do to implement this feature, it can't really be implemented "out of the box", the flutter developer needs to do a little bit of extra lifting.

This is because go_router is path based, there is fundamentally no such thing as 'multiple stacks' ever in memory. There is simply a path, and that will match to some stack of pages, and you can wrap those pages in an external tab menu, which gets you most of the way to nested nav in appearence, just not in behavior.

What's left to do, is the key feature people expect from this paradigm which is "each tab preserves it's state". This breaks down into 2 parts as far as I see:

  • Restore the correct page when switching to a tab (it should load the page it was showing previously, and remember the viewstack)
  • Have the page itself remember it's state (scroll position, selected sort options, etc)

The first thing can be done by having some imperative code that saves off the last viewed path for each tab, and when the tab is pressed, it tries to look up the last known page, and show that rather than the default. For example, the default url for tab1 might be /tab1/messages but it could link instead /tab1/messages/compose if that was the last recorded path for tab1. Given that later path, GoRouter would be smart enough to stack the Compose page on top of Messages and pop() would work as expected.

The second requirement is harder, having the stateful views remember/restore their state. I don't think there is actually a good solution for this that will work with GoRouter, unless you just have 1 top level pageRoute and all subsequent page routing is handled internally inside that widget. In theory the Restoration API could be extended to serve this use case I think. In the meantime it would probably have to be done via some form of persistent storage and having views manually restore their state when loaded, using an id (just like Restoration API does).

Couldn't that second requirement be accomplished with Offstage widgets and a Stack? (As described in the article at the top) That way when switching tabs the state wouldn't be lost.

@esDotDev
Copy link

esDotDev commented Oct 30, 2021

Ya, that's basically what I mean with "1 top level pageRoute and all subsequent page routing is handled internally inside that widget", like GoRouter is mapped to one persistent route, and an IndexedStack or something is handling the page-routing from there somehow? But then GoRouter is not doing much, except parsing some args for us and updating the browser location.

It's worth noting too, if you can access the navigation path history, then it's pretty trivial to have these menu buttons that know they are "/tab1", and can either link to their default path, or look up the last history location that contained their path. So that part is not really as cludgy as it sounds and you end up with the UX most clients seem to want.

Maybe there's a way with nested routers or navigators that I just don't understand.

@csells
Copy link
Owner Author

csells commented Oct 30, 2021

I'd love a sample of that...

@esDotDev
Copy link

esDotDev commented Oct 30, 2021

I do have a lib I made for almost this exact purpose https://pub.dev/packages/path_stack

It just wraps IndexedStack, but uses strings as keys instead of int, and supports nesting:
https://pub.dev/packages/path_stack

So all the pages are just kept in memory for free. I'll see what I can do for a little demo of combining this with GoRouter :)

@csells
Copy link
Owner Author

csells commented Oct 30, 2021

Nice!

@esDotDev
Copy link

esDotDev commented Oct 30, 2021

Thinking about it a bit more, my worry is that I don't think this is the right direction for GoRouter. We'd lose all the nice stacking logic you have, and have to reproduce most of the path parsing and guard logic.

Seems like the better approach is to just try and remember tabs, and restore page state manually somehow. It's a bit harder, but then everything should just click together.

@csells
Copy link
Owner Author

csells commented Oct 30, 2021

is there some of the useful bits of GoRouter that can be exposed to implement this functionality?

@esDotDev
Copy link

esDotDev commented Oct 30, 2021

As long as you expose the history of paths, then the tab btn stuff should be pretty trivial.

Restoration is the hard one, This line from the docs is interesting:
In addition to providing restoration data when the app is launched, restoration data may also be provided to a running app to restore it to a previous state (e.g. when the user hits the back/forward button in the web browser). When this happens, the RestorationManager notifies its listeners (added via addListener) that a new rootBucket is available. In response to the notification, listeners must stop using the old bucket and restore their state from the information in the new rootBucket.
https://api.flutter.dev/flutter/services/RestorationManager-class.html

If we could somehow trigger that restoration call each time we change routes, I think in theory we could get the "Stateful Tabs" paradigm working pretty neatly. Maybe it already does get called? I haven't played restoration API's for quite a while.

@csells
Copy link
Owner Author

csells commented Oct 30, 2021

I'm not sure what you mean by "history of paths". Do you mean the stack of routes that a location creates? Those trivial to expose.

@esDotDev
Copy link

I guess I should have said history of locations. The equivalent of your browser history. A list of strings, representing the chronological history of your current browsing session.

This would allow each tab to effectively "remember" it's last visited child page by just looking at the history, and checking for the most recent location, and loading that when the tab is pressed.

If you don't expose this, if would be easy enough to listen to the router, and record each new location as it comes in allowing us to generate this history stack manually. Nice if it were exposed though.

@csells
Copy link
Owner Author

csells commented Oct 30, 2021

go_router doesn't track this info. you can listen to changes on the GoRouter and pull out the location property as it changes.

@esDotDev
Copy link

Ok, ya as I mentioned I know goRouter is a changeNotifier, so we could always listen to it and do it manually.

Would be nice if it maintained this state though, since it's quite useful to be able to peer into history stack and do stuff with it, like we just demonstrated :D

@esDotDev
Copy link

VRouter provides something like this: https://pub.dev/documentation/vrouter/latest/vrouter/VHistory-class.html so these use cases are made a little easier.

@esDotDev
Copy link

Any thoughts on the more difficult aspect which is having routes be able to restore their state when loaded?

I wonder if there is some way we can trigger RestorationManager to run when a new route is shown, so we have a nice clean mechanism for routes to restore their state?

@csells
Copy link
Owner Author

csells commented Oct 30, 2021

Maybe we implement the UX first and then get the state restoration working after?

@aytunch
Copy link

aytunch commented Oct 31, 2021

I do have a lib I made for almost this exact purpose https://pub.dev/packages/path_stack

It just wraps IndexedStack, but uses strings as keys instead of int, and supports nesting: https://pub.dev/packages/path_stack

So all the pages are just kept in memory for free. I'll see what I can do for a little demo of combining this with GoRouter :)

@esDotDev
If we have Google maps with lots of markers in one of the bottom nav tabs and a video feed on another, etc, the Offstage + IndexedStack strategy to keep state would consume a lot of memory right? When you said "memory for free", I wonder if there is something I am missing from the picture Maybe Flutter does something clever behind the scenes? Because I sure don't want to keep the restoration states of all my pages manually.

@esDotDev
Copy link

esDotDev commented Oct 31, 2021

Yep, we're trading RAM for improved UX in this case. "memory for free", I just mean that the routes are cached in ram, so no extra work is needed for the dev, and all state is maintained, from viewstate to internal scroll state etc.

In my pathStack lib, I make it optional, so a route could say that it doesn't want to be persistent, so maybe you'd set the video view to be non persistent, and instead do a manual restoration on that page.

@esDotDev
Copy link

esDotDev commented Oct 31, 2021

Some digging this morning turned up this very interesting looking API:
https://api.flutter.dev/flutter/widgets/Navigator/restorablePush.html there is nothing on all of the internet about how to use it though. Guess it's time to play :)

Docs are pretty opaque, it states:
Unlike Routes pushed via push, Routes pushed with this method are restored during state restoration according to the rules outlined in the "State Restoration" section of Navigator.

If you jump over to see the rules, you get:
A Route added with the restorable imperative API (restorablePush, restorablePushNamed, and all other imperative methods with "restorable" in their name) restores its state if all routes below it up to and including the first Page-based route below it are restored. If there is no Page-based route below it, it only restores its state if all routes below it restore theirs

Which I don't really understand.

@esDotDev
Copy link

esDotDev commented Nov 2, 2021

Ya that's a bug because I wasn't properly assigning page keys, but I was too lazy to re-record. It works properly when page keys are assigned:

yeuaSZauES.mp4

Basically Navigator was getting confused, and thinking we were adding a new page on top of the old, rather than replacing both pages. Since as far as it could tell it was just 2 children, of type MaterialPage.

@esDotDev
Copy link

esDotDev commented Nov 2, 2021

Intersting comment in the src code here:
image

Seems like I wasn't too far off thinking restoration could be used for this. It seems very similar to the PageStorage API. I'm just not sure how it works, like how do views request state restoration to happen when a new route is pushed?

It seems the flutter team is only letting the OS drive these restores, when we want more manual control.

@esDotDev
Copy link

esDotDev commented Nov 2, 2021

Unfortunately it seems all flutter PageRoute extend ModalRoute, which add a PageStorage element, and no ability for you to provide your own bucket.

This means it's not possible for us to provide a single PageStorage outside the Navigator, as the pages will never be able to reach it. PageStorage.of(context), will always return the storage from the route, not the one at the top of the tree that we want for persistent storage.

Not a deal breaker, but it means each toplevel page view that wants to restore it's children needs to add some boilerplate like:

return PageStorage(
  bucket: GoRouter.of(context).storage.bucket,
  ....
)

Seems like restoration will be a better end-solution, as PageStorage is a little problematic. It has this rather major limitation, and also has some pretty nasty edge cases because of some odd design decisions: flutter/flutter#53040

@esDotDev
Copy link

esDotDev commented Nov 2, 2021

Thinking more about how GoRouter can support "stateful routes" in general, without needing any restoration/storage hassles, it fundamentally comes down to: can you cache the results of pageBuilder so that next time that same route is built, we get the cached version with intact state.

I think that comes down to a question of whether the child-widget of a Page can be cached somehow. Normally you would cache views like this with a Stack of Offstage widgets, but I'm not sure how to make that work when working with Pages.

If you can have stateful routes, and you can use a bit of logic to remember the last viewed page for any tab, then I think you get a really elegant and easy implementation of the "multiple nav stacks" paradigm, with full browser support.

Also, fwiw, I have logged an issue last April requesting support for this use case w/ Restoration
flutter/flutter#80303

@esDotDev
Copy link

esDotDev commented Nov 4, 2021

I created an issue specifically to discuss state preservation of routes, cause it's really it's own feature.

Curious if anyone has any bright ideas on this problem in particular:
#134

@csells csells mentioned this issue Nov 21, 2021
9 tasks
@davidmartos96
Copy link

I've created a small demo of a persistent bottom navigation with independent routes and helper methods to navigate across different tabs (similar to how the Play Store Console behaves with its Navigation Rail).

I don't know how feasible would be for go_router to implement something like this out of the box. This approach uses IndexedStack which persists the different children states in memory.

https://github.com/davidmartos96/go_router_bottom_nav_demo/tree/main/lib

@olof-dev
Copy link

For reference, in addition to Andrea Bizzotto's article mentioned at the start, there's also Hans Muller's take on nested navigation:
https://medium.com/flutter/getting-to-the-bottom-of-navigation-in-flutter-b3e440b9386

@techouse
Copy link

techouse commented Dec 15, 2021

https://github.com/davidmartos96/go_router_bottom_nav_demo/tree/main/lib

Thanx for the idea of using provided router delegates @davidmartos96 💟 I've implemented this approach in combination with a PageView (instead of an IndexedStack) and AutomaticKeepAliveClientMixin on the screens themselves and it works as expected. 👍🏻

@csells
Copy link
Owner Author

csells commented Dec 16, 2021

fyi @lulupointu on these approaches

@lulupointu
Copy link
Contributor

@csells yes I've read these before designing the Multi stack API.

State restoration would not be at the level that @esDotDev (since this would means caching every visited page). However the API uses IndexedStack (a lazy version actually) under the hood so switching between tabs would keep the state of the others (as described in the doc). So this and that would be possible.

@bahag-raesenerm
Copy link

@esDotDev how is the current state of this topic?
we'd really like to go with go_router but need this functionality ... we could also consider "to hack it" for a temporary solution but maybe we could also contribute somehow to bring things forward?

@esDotDev
Copy link

esDotDev commented Feb 9, 2022

For now, I think your best approach is to just forward a bunch of urls to the same Page, and then parse the url yourself inside that page, and set the index on an indexed stack. When you change tabs, you'll need to decide whether you want to try and remember the last viewed page for each tab, or not.

Unfortunately that means not using a lot of GR's path-parsing mechanisms as it just forwards /:path into your view, and you have to parse things from there.

@esDotDev
Copy link

esDotDev commented Feb 9, 2022

Multi-Stack API looks like the best idea to me, it does all of the above stuff automatically, and allows you to continue to declare complex paths with GR.

Where it might get a bit weird is the path-matching... maybe we can dig into any issues there. At the very least, it seems hard to explain in a simple way.

@johnpryan
Copy link
Collaborator

There are definitely some cases where using a nested Navigator makes sense (see Hans' article for an example). But I wonder if we should recommend configuring the inner Navigator using the page parameter based off of GoRouterState, since that is more declarative.

@johnpryan
Copy link
Collaborator

johnpryan commented Feb 11, 2022

Here's a gist that shows what I mean. The app uses a BottomNavigation bar and an AnimatedSwitcher to set up lateral navigation between two screens. The first screen (LibraryScreen) builds a nested Navigator with the pages parameter. When the route is /, it displays a single page, but when the route is /song/:songId it's songId field is non-null, and an additional page is provided to the inner Navigator to show the SongScreen too:

class LibraryScreen extends StatelessWidget {
  final String? songId;

  const LibraryScreen({
    this.songId,
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // Use a local variable to promote to a non-nullable type
    final songId = this.songId;

    return Navigator(
      onPopPage: (route, dynamic result) {
        final didPop = route.didPop(result);
        if (didPop) {
          // The user isn't viewing the song screen anymore. Go to the
          // parent screen.
          GoRouter.of(context).go('/');
        }
        return didPop;
      },
      pages: [
        MaterialPage(
          child: Scaffold(
            appBar: AppBar(),
            body: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Text(
                    'Home Screen',
                    style: Theme.of(context).textTheme.headline4,
                  ),
                  TextButton(
                    onPressed: () {
                      GoRouter.of(context).go('/song/123');
                    },
                    child: const Text('View song 123'),
                  ),
                ],
              ),
            ),
          ),
        ),
        if (songId != null) MaterialPage(child: SongScreen(songId: songId))
      ],
    );
  }
}

The problem is that once the user taps the "recents" tab in the BottomNavigationBar, the previous route might have been /song/1, and there's no obvious way to use go_router to store this state, but I'm not sure if it should.

@csells
Copy link
Owner Author

csells commented Feb 11, 2022

The scenario that I get asked about all the time is for a tabbed ui (whether on the top or the bottom) to have a nav stack per tab and that switching between tabs should remember the users spot in the nav stack for each tab. I've come to understand that this is what "nested nav" means to people and it's a common app requirement that go_router should support.

@II11II
Copy link

II11II commented Feb 12, 2022

I've created a small demo of a persistent bottom navigation with independent routes and helper methods to navigate across different tabs (similar to how the Play Store Console behaves with its Navigation Rail).

I don't know how feasible would be for go_router to implement something like this out of the box. This approach uses IndexedStack which persists the different children states in memory.

https://github.com/davidmartos96/go_router_bottom_nav_demo/tree/main/lib

Hi @davidmartos96
Thank you for good idea, but app loses deep link functionality
image

@davidmartos96
Copy link

I've created a small demo of a persistent bottom navigation with independent routes and helper methods to navigate across different tabs (similar to how the Play Store Console behaves with its Navigation Rail).

I don't know how feasible would be for go_router to implement something like this out of the box. This approach uses IndexedStack which persists the different children states in memory.

https://github.com/davidmartos96/go_router_bottom_nav_demo/tree/main/lib

Hi @davidmartos96
Thank you for good idea, but app loses deep link functionality
image

Yes, unfortunately that approach doesn't quite work for web deep linking.

@esDotDev
Copy link

esDotDev commented Feb 12, 2022

The problem is that once the user taps the "recents" tab in the BottomNavigationBar, the previous route might have been /song/1, and there's no obvious way to use go_router to store this state, but I'm not sure if it should.

Right, this is one of the core pieces of logic provided by lulu's tabbed router example. Someone needs to remember what was the last visited url for each tab, so that when the tab is clicked, it can link into the last loaded url for that tab (or fallback to the "home tab")

The simplest way I've done this in the past, is to just addListener onto GR, and save a list of url changes. Then when a tab is pressed, (eg "settings") it can just check that history and grab the last url that .startsWith('/settings') and this works pretty nicely with only a few lines of code.

This is an alternative to actually having real nested navigators, each with their own stack of pages. I'm not sure how that would actually work when using names routes... this approach is more about faking it so it gives the appearance or general UX of individual navigators.

@mark8044
Copy link

Thanks for everyone work on this issue. Solving this would make go_router the router to use.

One issue with other implementations of BottomNavigation and IndexedStacks is that on iOS you lose access to the scroll-to-top with status bar tap functionality. This is because of issues with the PrimaryScrollController from a root stack eating up those clicks. And if you try to pass PSC to all the different stacks then they all scroll up.

So far using CupertinoTabScaffold/CupertinoTabBar doesn't seem to carry the problem (but has so many other problems with routers).

Just throwing this out there as something to keep in mind with your implementations. If its working with your implementation, then mores the better!

On the edge of my app to see if this comes to go_router as everything else seems to work really well and easy

@techouse
Copy link

@mark8044 Yeah, I've faced the same issue and just done what Facebook does in their app. A repeated tap on the currently active bottom nav takes you back to the top 🤷

@davidmartos96
Copy link

davidmartos96 commented Feb 19, 2022

Thanks for everyone work on this issue. Solving this would make go_router the router to use.

One issue with other implementations of BottomNavigation and IndexedStacks is that on iOS you lose access to the scroll-to-top with status bar tap functionality. This is because of issues with the PrimaryScrollController from a root stack eating up those clicks. And if you try to pass PSC to all the different stacks then they all scroll up.

So far using CupertinoTabScaffold/CupertinoTabBar doesn't seem to carry the problem (but has so many other problems with routers).

Just throwing this out there as something to keep in mind with your implementations. If its working with your implementation, then mores the better!

On the edge of my app to see if this comes to go_router as everything else seems to work really well and easy

I haven't tried, but couldn't you wrap every tab with a PrimaryScrollController widget and pass a different controller to each tab?

@mark8044
Copy link

Thanks for everyone work on this issue. Solving this would make go_router the router to use.
One issue with other implementations of BottomNavigation and IndexedStacks is that on iOS you lose access to the scroll-to-top with status bar tap functionality. This is because of issues with the PrimaryScrollController from a root stack eating up those clicks. And if you try to pass PSC to all the different stacks then they all scroll up.
So far using CupertinoTabScaffold/CupertinoTabBar doesn't seem to carry the problem (but has so many other problems with routers).
Just throwing this out there as something to keep in mind with your implementations. If its working with your implementation, then mores the better!
On the edge of my app to see if this comes to go_router as everything else seems to work really well and easy

I haven't tried, but couldn't you wrap every tab with a PrimaryScrollController widget and pass a different controller to each tab?

I think the solution lies within that idea.

The problem so far with bottom nav implementations is you have a structure like this:

- Scaffold / PSC 
    - BottomNavigation -- Stack
          - Scaffold / PSC
               - Your page

So the BottomNav implementation uses as IndexStack that lies on top of a Scaffold and the clicks just don't pass to where you want them to.

Quite a few people have brought this up with flutter team, with ideas to allow us to access the root PSC, but that too is imperfect. Ive manually accessed the PSC before setting up a BottomNavigationBar and then passed it into the various tabs, and then the scroll to top does work, but then the problem is every list in every tab simultaneous scroll up, so that is also useless.

Whats interesting is things like WebView and InAppWebView work perfectly no mater where you put them in the tree, so I believe they are doing something along the lines of what you have mentioned. And of course CupertinoTabScaffold/CupertinoTabBar doesn't have this issue

@wwwdata
Copy link

wwwdata commented Feb 19, 2022

Thanks for everyone work on this issue. Solving this would make go_router the router to use.

One issue with other implementations of BottomNavigation and IndexedStacks is that on iOS you lose access to the scroll-to-top with status bar tap functionality. This is because of issues with the PrimaryScrollController from a root stack eating up those clicks. And if you try to pass PSC to all the different stacks then they all scroll up.

So far using CupertinoTabScaffold/CupertinoTabBar doesn't seem to carry the problem (but has so many other problems with routers).

Just throwing this out there as something to keep in mind with your implementations. If its working with your implementation, then mores the better!

On the edge of my app to see if this comes to go_router as everything else seems to work really well and easy

I could easily solve the scroll to top problem by using the following package: https://pub.dev/packages/scrolls_to_top

@Tananga
Copy link

Tananga commented Feb 22, 2022

@csells We need something like this: https://youtu.be/9oH42_Axr3Q.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests