State Restoration in flutter | restore counter app and textfield
flutter

State restoration is very crucial for your app. If you are reading this blog post, then I bet that you need this cool tech.
So what is state restoration? Let’s understand it with a little story.
Imagine you are using an app and playing a quiz game. In the middle of the game, someone calls you for something urgent. But you are on the final round of your quiz game you don’t want to lose your progress.
Now in this condition, if your app doesn’t support state restoration, once you close your app, your app goes to background. After some time of inactivity, the operating system kills your app.
Imagine you have to lose your progress in the app only because the developer hasn’t implemented state restoration in the app. This is a poor UX in my opinion. Now the question is how to fix it.
What is flutter state restoration?
Let me introduce you to the flutter state restoration techniques. A combination of widgets, mixins and functions, that let us developers seamlessly integrate state restoration across android and ios apps.
App state restoration is very critical from the perspective of user experience. If you properly implement state restoration techniques in your app and follow all the best practices, then surely you can deliver a great user experience.
In this blog post, we will discuss 2 use cases and examples of state restoration. These are,
- State restoration of flutter counter app.
- State restoration of a text field.
This will be a very long article. Bring some tea or coffee and enjoy the article.
Some basics and terminology
Before getting started, let us understand some basic terminology used in flutter state restoration.
- RestorationScopeId:
restorationScopeIdis a property of MaterialApp and you have to set a unique string for your app asrestorationScopeId. - RestorationMixin:
RestorationMixinis used to restore the state of a stateful widget. Remember, state restoration is only applicable for thestatefulwidgets. This restoration mixin does all the heavy lifting for us. - Restorable property: We use
RestorablePropertyinstead of a normal variable inside astatefulwidget. The state is stored inside aRestorablePropertyduring runtime and it knows how to restore its value during startup. For example in the flutter counter app, we useRestorableIntinstead of a normal integer as a type of the counter value. By usingRestorableInt, nowRestorationMixincan safely store and retrieved the counter value. - Restoration ID:
RestorationMixinsaves the state of thestatefulwidget inside aRestorationBucket. SoRestorationMixinneeds a unique namespace inside theRestorationBucketto save all the registered properties. For that purpose, we have to declare arestorationId. - registerForRestoration:
registerForRestorationis a function that is used to register a restorable property for state restoration. It requires a uniquerestorationIdso thatRestorationMixincan find its location in theRestorationBucket. - RestorationBucket:
RestorationBucketis the storage where all the restoration data is saved by theRestorationMixin. During app initialization,RestorationMixinread the values stored insideRestorationMixinand restore the state.
We can visualize the hierarchy with the help of this diagram.

State restoration of flutter counter app
We all know the flutter counter app. This is the default app flutter gives us when we create a new flutter project.
To demonstrate the working process and implementation of flutter state restoration, I think the counter app is the best example anyone can give you.
This is the code you get under your main.dart file while you create your flutter app. I have removed all the comments to reduce the clutter.
1import 'package:flutter/material.dart';23void main() {4 runApp(const MyApp());5}67class MyApp extends StatelessWidget {8 const MyApp({Key? key}) : super(key: key);91011 Widget build(BuildContext context) {12 return MaterialApp(13 title: 'Flutter Demo',14 theme: ThemeData(15 primarySwatch: Colors.blue,16 ),17 home: const MyHomePage(title: 'Flutter Demo Home Page'),18 );19 }20}2122class MyHomePage extends StatefulWidget {23 const MyHomePage({Key? key, required this.title}) : super(key: key);24 final String title;252627 State<MyHomePage> createState() => _MyHomePageState();28}2930class _MyHomePageState extends State<MyHomePage> {31 int _counter = 0;3233 void _incrementCounter() {34 setState(() {35 _counter++;36 });37 }383940 Widget build(BuildContext context) {41 return Scaffold(42 appBar: AppBar(43 title: Text(widget.title),44 ),45 body: Center(46 child: Column(47 mainAxisAlignment: MainAxisAlignment.center,48 children: <Widget>[49 const Text(50 'You have pushed the button this many times:',51 ),52 Text(53 '$_counter',54 style: Theme.of(context).textTheme.headline4,55 ),56 ],57 ),58 ),59 floatingActionButton: FloatingActionButton(60 onPressed: _incrementCounter,61 tooltip: 'Increment',62 child: const Icon(Icons.add),63 ),64 );65 }66}67
First, add a unique restorationScopeId inside your material app. It requires a string. Be creative and come up with a unique name.
1MaterialApp(2 restorationScopeId: 'app',3 title: 'Flutter Demo',4 theme: ThemeData(5 primarySwatch: Colors.blue,6 ),7 home: const MyHomePage(title: 'Flutter Demo Home Page'),8 );9
Now we have to add the RestorationMixin. It enables us to store the state of the stateful widget in the restoration bucket.
Here we add the RestorationMixin with _MyHomePageState class. Be cautious, we have to add the mixin with _MyHomePageState not with MyHomePage.
1class _MyHomePageState extends State<MyHomePage> with RestorationMixin{23}4
Now we have to add a RestorationProperty to store state. For that, we replace the type of _counter variable from an int into RestorableInt.
1class _MyHomePageState extends State<MyHomePage> with RestorationMixin{2 RestorableInt _counter = RestorableInt(0);3}4
As _counter is now a RestorableInt, we can get the integer value of the _counter variable by using _counter.value property.
Let’s change our code to adopt the new behavior of _counter variable.
1void _incrementCounter() {2 setState(() {3 _counter.value++;4 });5 }678 Widget build(BuildContext context) {9 return Scaffold(10 appBar: AppBar(11 title: Text(widget.title),12 ),13 body: Center(14 child: Column(15 mainAxisAlignment: MainAxisAlignment.center,16 children: <Widget>[17 const Text(18 'You have pushed the button this many times:',19 ),20 Text(21 '${_counter.value}',22 style: Theme.of(context).textTheme.headline4,23 ),24 ],25 ),26 ),27 floatingActionButton: FloatingActionButton(28 onPressed: _incrementCounter,29 tooltip: 'Increment',30 child: const Icon(Icons.add),31 ),32 );33 }34
Now we have to make 2 overrides inside out stateless widgets. At first, we have to provide a unique restoration Id. This restorationId reserves space inside the restoration bucket for our widget.
12 String? get restorationId =>"homepagestate";3
Now we override a function named restoreState. We register our restorable property (eg. _counter) for restoration. Without registration, state restoration for restorable properties will not work.
12 void restoreState(RestorationBucket? oldBucket, bool initialRestore) {3 registerForRestoration(_counter, "counter");4 }5
Till now we have successfully modified our counter app. Now our app supports state restoration. The final code for our app looks like this.
1import 'package:flutter/material.dart';23void main() {4 runApp(const MyApp());5}67class MyApp extends StatelessWidget {8 const MyApp({Key? key}) : super(key: key);910 // This widget is the root of your application.1112 Widget build(BuildContext context) {13 return MaterialApp(14 title: 'Flutter Demo',15 theme: ThemeData(16 primarySwatch: Colors.blue,17 ),18 home: const MyHomePage(title: 'Flutter Demo Home Page'),19 );20 }21}2223class MyHomePage extends StatefulWidget {24 const MyHomePage({Key? key, required this.title}) : super(key: key);25 final String title;262728 State<MyHomePage> createState() => _MyHomePageState();29}3031class _MyHomePageState extends State<MyHomePage> with RestorationMixin{32 RestorableInt _counter = RestorableInt(0);3334 void _incrementCounter() {35 setState(() {36 _counter.value++;37 });38 }394041 Widget build(BuildContext context) {42 return Scaffold(43 appBar: AppBar(44 title: Text(widget.title),45 ),46 body: Center(47 child: Column(48 mainAxisAlignment: MainAxisAlignment.center,49 children: <Widget>[50 const Text(51 'You have pushed the button this many times:',52 ),53 Text(54 '${_counter.value}',55 style: Theme.of(context).textTheme.headline4,56 ),57 ],58 ),59 ),60 floatingActionButton: FloatingActionButton(61 onPressed: _incrementCounter,62 tooltip: 'Increment',63 child: const Icon(Icons.add),64 ),65 );66 }676869 String? get restorationId =>"homepagestate";707172 void restoreState(RestorationBucket? oldBucket, bool initialRestore) {73 registerForRestoration(_counter, "counter");74 }75}76
State restoration of a text field
To restore the value of a text field, we need a text editing controller. To add the state restoration behavior, we use RestorableTextEditingController.
Inside a StatefulWidget we declare our RestorableTextEditingController.
1late RestorableTextEditingController _controller;2
Now we initialize this controller inside the initState. Also, don’t forget to add the dispose method. Otherwise, our app will have some serious memory leakage issues.
In the text field of RestorableTextEditingController you can specify any default text to start with. For now, I leave it as a string without any value.
12 void initState() {3 _controller = RestorableTextEditingController(text: "");4 super.initState();5 }678 void dispose() {9 _controller.dispose();10 super.dispose();11 }12
Now inside your TextField add the _controller we have defiled above.
12 Widget build(BuildContext context) {3 return Scaffold(4 appBar: AppBar(5 title: Text(widget.title),6 ),7 body: TextField(8 controller: _controller.value,9 ),10 );11 }12
Now your TextField becomes restorable. Test it inside your emulator or a real device.
Issues
This is a built-in state restoration technique flutter gives us out of the box. Surely we can use these restoration techniques in production.
But all these features come up with a drawback. we can’t use it inside a stateless widget. As per the documentation, RestorationMixin only restores the state of the Stateful widgets.
As the large flutter apps use other state management like bloc, getx or riverpods, only stateful widget support is not enough. Hope in the future, we can see a more robust implementation of flutter restoration.
Conclusion
For a production-ready app, the state restoration technique is a must to have. It gives a nice user experience. If your app restores the app state smoothly, the quality and trustworthiness of the app will increase.
To make your more user-friendly, you can use semantics. Semantics helps disabled people to properly use your app. Accessibility is the major reason for adding semantics labels.
Don’t know how to do this? Read my new article on How to use semantics in Flutter.
If you want to get updated with this type of latest tech article, make sure to subscribe to this blog and follow me on Twitter.