csells / go_router

The purpose of the go_router for Flutter is to use declarative routes to reduce complexity, regardless of the platform you're targeting (mobile, web, desktop), handling deep linking from Android, iOS and the web while still allowing an easy-to-use developer experience.
https://gorouter.dev
442 stars 96 forks source link

It would be nice if routes could automatically persist their state #134

Closed esDotDev closed 2 years ago

esDotDev commented 2 years ago

Currently there is no automatic way to have your routes remember their internal state as you navigate between them. This is often a desired UX feature as it is seen commonly on mobile and desktop platforms. For example, when I change tabs in a desktop app like Steam, it remembers my scroll position and selected sorting features (ephemeral view state).

The canonical way to do this in flutter is to use an Offstage widget, or something that wraps one, like an IndexedStack. The classic "nested navigator" pattern is a variation on this technique, where multiple stateful navigators are kept in memory using a TabBarView or IndexedStack.

I'm not sure if this is even possible with GoRouter, but would like to put this idea out there.

This is the core problem to solve. Given:

routes: [
  GoRoute(path: 'page1', pageBuilder: (_, state) => MaterialPage(key: state.pageKey, child: Page1())),
  GoRoute(path: 'page2', pageBuilder: (_, state) => MaterialPage(key: state.pageKey, child: Page2())),
]

When page1 is the path, the result of () => MaterialPage(key: state.pageKey, child: Page2()) is stashed Offstage somehow. Vice versa when `page2 is rendered. It wouldn't load everything at once, but rather lazy load and "remember" pages it had seen before. Likely there should be an option to opt-out certain views for persistence.

csells commented 2 years ago

In the README today, there are docs on how to persist widget state for nest nav. I haven't tested it, but the same should work for any kind of nav. Give it a try and let me know.

esDotDev commented 2 years ago

That shows a specific little trick to use a shared view for multiple paths, ie multiple path combinations goto FamilyTabsPage and it takes a param, internally stashing some set of "sub-routes" offstage (using an internal TabBarView).

But the ask here is really, can we keep any route in memory without any special workarounds or imposed structure.

Simply put: can I load a page, and then get the same one back next time I reload it, regardless of what is inside the pageBuilder.

csells commented 2 years ago

I hesitate to override, circumvent or otherwise reimplement the caching logic that Flutter itself provides via keys. That seems out of scope for a custom router package.

esDotDev commented 2 years ago

Being that we rely on the Navigator to switch pages via routes, and the router to build the MaterialPage instances, it's really only the router that could do this, if it's even possible.

That's sort of the ask, is there a way that MaterialPage can be cached when not being viewed? I don't know the answer.

Flutter does it when you Push a new route on top of another when persistState is set to true, so it seems like it might be possible at some level. ie, if I pop that new route, my old route slides back into view, with all of it's state preserved, so flutter is capable at some level of caching these routes when they are not in view.

rydmike commented 2 years ago

Good points @esDotDev, I have been wondering the same and also thinking this would be a really useful feature.

What I have done in one or two cases when using a custom plain indexed main navigation menu that need to keep state of its 6...8 main routes on desktop. Was to copy the code that does it for the Cupertino navigator, with some minor mods. That gave me that multiple route storage container as a separate widget that I could use as my nested navs when using the classic navigator, plus I tested it with Routemaster too on the indexed navigation it has.

While this worked, it is basically similar to the TabView nested nav example as well using same principle as you commented about above. It is also an external solution to the actual navigation solution, so definitely not ideal, felt more like hack.