Repository navigation
[go_router] Preserve the state of routes #99124
Description
Activity
- addedpackageflutter/packages repository. See also p: labels.flutter/packages repository. See also p: labels.p: go_routerThe go_router packageThe go_router package
on Feb 25, 2022 - addedP3Issues that are less important to the Flutter projectIssues that are less important to the Flutter projectc: new featureNothing broken; request for a new capabilityNothing broken; request for a new capability
on Mar 1, 2022 - changed the title
[-][go_router] It would be nice if routes could automatically persist their state[/-][+][go_router] Preserve the state of routes[/+]on Apr 6, 2022 @chunhtai @johnpryan
Not sure how to upvote, but I would be happy if I could.
Perhaps if any of you can provide some clarity about it with 5.0 imminent (?)
Is it on the road map? and I just need to be more patient?
Should the community try to fix it? (perhaps even me? 😅 )We are currently still in the planning phase. Given this is one of the higher priority issue, I think it will likely to be worked on first. If you would like to work on it, that will be great, too.
Reacted by mono — Masayuki Ono, chrisharris77, max-insight, Adrien Bonnin, Dr Sunil Mathew, Emmanuel Ayo Adedokun, arnair, Chris Yi, Social Jawn, David Darrell and 20 moreReacted by Ariel Ben Horesh, mono — Masayuki Ono, Hadi, Marcin Wróbel, Nikita Stetskii and Felix JordanReacted by Sigitas, WH-Creations, Rahmi Tufanoglu and Vellianefwiw, when this was original discussed there did seem to be quite a bit of overlap here and the Restoration API used to restore stateful views when they are killed by the OS.
It seems like a with a little bit of tweaking that API could be leveraged to give the impression of stateful routes, without actually needing to hold all the views/navigators offstage. For the most part we are generally just looking to maintain things like scroll position, textfield entries, animations, etc all of which already support restoration API... and then there is small bits of internal view-state state like sorting options, or loaded data, which we could manually configure w/ RestorableInt, RestorableBool etc.
Specifically we would need some sort of
restorationManager.restore(State)API, and maybe aRestoreStateOnMountMixinthat automatically restores on initState.Just food for thought, obviously would be great if things just persisted fully with no work from the developer. But it wouldn't be too bad if we have to use Restoration API, and then at least all our views will also work well when killed off by a mobile OS (or even an app crash) so you'd get two birds with one stone at least.
This also has the benefit of being more scalable, each view can inflate it's data when shown, without trying to hold everything in RAM.
Reacted by Jarle Stabell and Maruf HassanInstead of attempting to save and restore state, would it be an option to build a solution where a nested GoRouter object is used to preserve a navigation stack, while at the same time establishing a relationship with the parent GoRouter to facilitate deep linking etc? I may be wrong, but I think this is what a lot of people expected #99126 to accomplish.
For instance, Beamer does something like this. Here is an example with bottom navigation.
I don't know, maybe this has already been discussed and dismissed, if so I'm sorry for the noise.
Reacted by Ali-FadaeiReacted by J and Gabriel BetancourtAs of the new
go_routerversion5.0.0I refactored our app from usingIndexedStackto actually using theShellRouteofgo_routerfor our bottom navigation logic. It turned out that no state was preserved, which didn't make sense. When an app has a bottom navigation menu, we expect every tab to preserve its state while changing tabs.Does someone have a solution for this?
Reacted by suragch, Maksim Lin, Jaween Ediriweera, guidoec, Peter Aleksander Bizjak, Henry (Haim) Benhaim, José Daniel Calle Briceño, Youcef Bousmaha, Ahliman HUSEYNOV, Zixun Wang and 7 moreReacted by 1l0, Agon Noga, guidoec, Saqib Shafin, Ska Lee, Muccy, Maxime, WH-Creations, Manuel Plavsic, ninebitz and 7 moreMy guess is that the scaffolding is in place for this now that there is proper nested Navigator support w/ ShellRoute. I just thought I would bring up the RestorationAPI here, because it's a different way to tackle the problem that might co-exist with the ability to stash views in RAM.
I guess that would be a different feature, more like "Restore state of routes", rather than "preserve".
The end UX would be the same though: tabs and pages appear to hold their state
fwiw, I created an issue on this here: #80303 about 18mths ago, it seems like we really are so close to enabling "on demand" restoration with
StatefulWidgetsbut it's not possible due to unfortunate design choices in the Restoration API (or I'm just missing it).By way of example, you could imagine something like this:
mixin SaveRestoreStateMixin on State, RestorationMixin { // These two calls are missing, they would need to signal to all descendants to save/restore void initState() => // restorationManager.restoreState(this); void dispose() => // restorationManager.saveState(this) }
With that functionality, we can have restorable views really easy, like:
class _MyViewState extends State<MyView> with RestorationMixin, SaveRestoreStateMixin { // Example of a custom piece of view-level state, that would be restored when page is loaded again RestorableBool _sortAsc = RestorableBool(false); @override String? get restorationId => 'myView'; @override void restoreState(RestorationBucket? oldBucket, bool initialRestore) { registerForRestoration(_sortAsc, '_sortAsc'); } @override Widget build(BuildContext context) { return Column( children: [ // This textfield would restore it's internal state automatically TextField(restorationId: '$restorationId-search'); // Use restored view-level state for my list sorting. // The descendant `Scrollable` would restore it's own scroll position automatically as long as it is given a restorationId MyList(sortAsc: _sortAsc.value, restorationId: '$restorationId-scrollPos'), ] ) } }
All descendant widgets like lists or textfields would maintain their state since Restoration is built into the SDK. Having restorable view-level state is fairly simple as shown ^. For more complex data, it's likely that is hoisted up out of the widget tree anyways, and does not need to be persisted at the view/widget level.
This strikes me as fairly elegant. It adds about 6 lines to a view for basic restoration support, and then 1 extra line for each view-level property that needs to be restored. In many cases simply adding the Mixins and a restorationId would be enough to give a view the impression of being preserved.
Reacted by Jarle StabellYeah, I think I have stumbled over that issue before, and I agree that a state restoration based solution could possibly be used as a clean way of supporting "persistent" navigation stacks in for instance a BottomNavigationBar, without changing the intended way of configuring routes in go_router too much. I haven't used state restoration in Flutter myself that much though, but it seems like many parts that could form the foundation for such an implementation already exists in Flutter, even though this is perhaps a slightly different use case.
I can however imagine that in some cases (i.e. when using complex view hierarchies, logic and state, along with deeper navigation stacks etc), it would be easier and less complex to just keep the views and navigation stacks in memory. And in that case, a solution with separate, nested routers (like the Beamer example) might be preferable. Of course, nested GoRouters already work on a basic level, but support for things like deep linking are missing (this demo/example for instance is using nested GoRouters, without deep linking support).
But I guess the biggest question is how go_router could be modified to support these two different approaches. For the second approach at least, I imagine that it's more about documenting a particular usage pattern (with a good example), than making large changes in go_router.
Reacted by 1l0 and Maruf Hassan9 remaining items
any update on it ? please we required this feature
Reacted by Benjamin, Chit Ye Aung, Ogün, Velliane, Lyes , Guillaume Le Martret, Mohan, khoadng, André Sait Eppeland, Danniel Alvianto and 51 moreIt's really weird that there is no clean and especially official solution for this problem, which is still important and is even supported by native languages like swift...
Reacted by George Dicu, Dima, Glader, osmely, Garaba-PureCommit and Yeon-gil Kang(강연길)You can read updates and such in the linked pull request
Reacted by George Dicu and Jason Dean Lessenich@chunhtai this issue can finally get closed? :)
Yes, I think we can close this now. Pull request flutter/packages#2650 is merged and published as version 7.1.0
Reacted by Ahliman HUSEYNOV, Luca Venir and GIGASThis thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of
flutter doctor -vand a minimal reproduction of the issue.- locked as resolved and limited conversation to collaborators
on Jun 6, 2023
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsDone
copied from csells/go_router#134
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:
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.