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
Allow RestorationAPI to restore any StatefulWidget at runtime #80303
Comments
In terms of use cases, this is widely needed when it comes to app-routing right now, many of the app routing strategies lose state when changing routes with Navigator, and it's cumbersome to implement these basic framework restorations ourselves (restoring our own state is fairly easy, cause we can just hoist that up. But restoring every textField, scroller or AnimationController is a major pain point) |
Any thoughts on this @goderbauer? It seems we're extremely close to being able to do this right now... I just can't figure out how to trigger the Restoration on the state. This would really be a game changer when it comes to creating better UX with Flutter and Nav 2.0. Users want their view states to be saved between routes and currently it's very cumbersome. Maybe it's possible already? As a basic example I'd like this view to restore the count each time it loads (the count should persist), but it always resets: JqBbr4Aets.mp4class App extends StatelessWidget {
const App({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) => MaterialApp(restorationScopeId: 'app', home: CounterPage());
}
class CounterPage extends StatefulWidget {
@override
State<CounterPage> createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> with RestorationMixin {
RestorableInt _count = RestorableInt(0);
@override
String? get restorationId => 'page1';
@override
void initState() {
super.initState();
// TODO: We need some call here, to tell RestorationManager to give us the latest data.
}
@override
void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
// would like this to fetch the last saved value for this id
registerForRestoration(_count, 'count');
}
void _incrementCounter() => setState(() => _count.value++);
// Keep pushing the same page to the navigator, it should be able to persist it's state values
void _openNewPage() => Navigator.of(context).push<void>(MaterialPageRoute(builder: (_) => CounterPage()));
@override
Widget build(BuildContext context) => Column(children: [
OutlinedButton(onPressed: _incrementCounter, child: Text('count=${_count.value}')),
OutlinedButton(onPressed: _openNewPage, child: Text('Load Page')),
]);
} |
@goderbauer May we have your thought in case this is easy :) |
This may be a memory hazard. Right now restoration only store the current state in memory. so once a statefulwidget is destroyed, the state will be gone. If we enable this, the system need to try to preserve every state that has the restoration mixin without knowing whether it will be reuse in the future. |
There could be a memory limit where the oldest items are purged when a limit is reached? Seems we're usually storing simple primitives here, so in (common) practice it would likely not consume a ton of space? |
I think it will be hard to have a default limit or even have this as a default behavior, I think what flutter can do is to provide a hook or an API to customize how state is store and read so that a third party package can decide how it is used. |
We are currently using an |
I think of it more, I don't think we need to get thing tangled up with restoration API, we could have a special widget that will prevent subtree to be destroyed and kept it in memory secretly until it was created again. We will still run into the risk about it gets abused and cause the OOM though. This will require change to framework because it currently don't allow dangling Elements or Rendering objects. |
@chunhtai if I understand your strategy correctly, you are talking about a widget similar to |
No, something like PreserveState(
uniqueTag: ..
child: TextField
), what will happen is that once this widget is ever in the widget tree, its state will persist even if it is removed from tree later. So you frame 1, you have Scaffold(
body: PreserveState(
uniqueTag: 'abc',
child: TextField
),
) and they user type something in TextField after several frame, tree remove the textfield for some UI change Scaffold(
body: SomeWidget()
) and then after several frames, tree add the textfield back for some UI change Scaffold(
body: PreserveState(
uniqueTag: 'abc',
child: TextField
),
) it will pick up the old state and fill the user typed text back to TextField |
Thank you for the demonstration. It is very promising.
So even if If this was not What will happen behind the scenes of |
If we go with AutomaticKeepAlive route, the elements and renderobject will be kept alive, but in reasonable lifecycle state. so in theory, the videoplay if not pause, may still consuming resource, but it really depends on the implementation. This is just one idea I came up, there obvious need more thought on how to make it less dangerous to use. Another way would be PreserveState only preserve restorable state, but that will require subtree to opt in to state restoration. This will store less data in memory to mitigate OOM, but it is still possible thing may go out of control. |
This would be a great addition to the SDK, yes it's a little dangerous but it provides so much power and utility. For years I've wished for some other way to keep views in memory other than Offstage widget. We would just need some basic API to purge by ID, or purge all memory. |
When switching navigation routes it is often desired to retain various view state such as ScrollPosition, TextFields and Animation state.
Currently this requires using something like an
IndexedStack
, where all Widgets are kept in the tree and in memory. This is really not conducive to changing routes with Navigator or MaterialApp.router.It would be nice if somehow we could instead use the StateRestoration API to handle this. Ideally this would be syncronous, and restore data from RAM rather than disk, at least for the most recently cached states.
Desired Result:
When a state is disposed, it saves it's restorable fields. When a state with the same id is loaded again, it fetches any restored state it can. Ideally there is some way we can skip
initState()
here as well. Or incorporate it into it:The text was updated successfully, but these errors were encountered: