State Restoration of Flutter App
- Getting Started
- Testing on Android
- Testing on iOS
- Discovering the Restorable States
- Implementing State Restoration
- Enabling State Restoration on iOS
- Adding RestorationMixin
- Implementing the Restorable ToDo Item List
- Restoring Main Page
- Restore the Scroll Position
- Implementing the Restorable Route Returning Result
- Implementing Simple Restorable Properties
- Adding State to Date Picker Restoration
- Where to Go From Here?
Android and iOS can interrupt app processes to optimize resource usage. The system can kill apps in the background to gain more memory and CPU for the foreground. A killed app will start from scratch when a user brings it back. However, the user expects to see the same app state as when they left it instead of starting over again.
In this tutorial, you’ll see how to preserve the state of Flutter apps when the system decides to kill them. In the process, you’ll learn how to:
- Set up the environment.
- Discover the restorable states.
- Implement apps with state restoration.
- Test state restoration.
Download the starter project by clicking Download materials at the top or bottom of the tutorial.
In this tutorial, you’ll build a ToDo list app that lets you add a title and date for each item. Then, you’ll add the state restoration functionality, so you don’t lose any important data if the app closes unexpectedly.
First, you’ll explore the simple app. Open and run the starter project. You can use Control-R in Android Studio. Press the plus button and type a title in the Item details dialog.
A dialog on the top of the navigation stack and the text of the item title make up the in-memory state. So, now you’ll test the (lack of) restoration! You have to do it separately for both platforms.
Testing on Android
Go to Developer settings and turn on the option Don’t keep activities. Then, bring your app to the front. You’ll see the state loss — the app starts from scratch, and the dialog isn’t restored:
To simulate the restoration of a process, you have to send your app to the background. Then, bring another app to the foreground. Finally, return to your app. Returning from the recent app switcher without touching any other app isn’t enough. Also, don’t swipe out your app from the recents. The system won’t restore the state after that.
Testing on iOS
iOS doesn’t have the option to enforce process killing like Android. You have to perform some manual work. Start by opening ios/Runner.xcworkspace in Xcode. Set the Build Configuration to Profile, as on the screenshot below:
Note that building the app in Profile mode takes more time than in Debug. In the case of the simple app from this tutorial, it should have no measurable impact. But, when you’re working on larger projects, you may want to use Debug mode by default. In such cases, you can switch to Profile mode only when needed.
Next, press the play button (or Command-R, not Control like in Android Studio!) to run the app. Press the plus button and type a title in the Item details modal. Send the app to the background by pressing the Home button or performing a gesture. Press the stop button (or Command-.) in Xcode. And finally, reopen the app by tapping its icon. Don’t use Xcode to launch the app at this stage!
Discovering the Restorable States
Before you begin coding, think of what exactly the restorable parts of your app are. A good starting point is to look for
StatefulWidgets. As the name suggests, they should contain mutable states. Note that only the in-memory state matters for restoration purposes. Look at the simple example with the checkbox:
Here, you save the checked state instantly in a persistent way, somewhere like the local database, file or backend. So, it makes no sense to include it in the restoration logic, even if a checkbox is inside
StatefulWidget. Now, look at the second example with a checkbox and a button to commit its state:
In this case, the state between tapping a checkbox and a button exists only in memory. So, it should be the subject of restoration. Other common sources of the restorable state include:
TextField(along with text obscuring states)
- Expandable and collapsible widgets (e.g.,
- Scrollable containers (e.g.,
Note the last bullet. The scrollable container may be inside the
StatelessWidget. Yet, its scroll position is an in-memory state. In such a case, you may want to convert your widget to a
StatefulWidget and add a field for the
ScrollController into its
The restorable state covers more than just the widget’s content. The navigation stack is also an in-memory state. Users expect to return to the same place they were before leaving the app. Note that dialogs — like pop-ups and modals — are on the stack too.
Implementing State Restoration
Finally, you can get your hands dirty by locating
MaterialApp in main.dart. Replace
// TODO: replace with restorableScopeId with the following line of code:
This can be any non-nullable string. If you want to test the changes done to the app, stop the app and rerun it with the help of Control-R or Command-R on macOS. Take a closer look, and you’ll see that there’s no visible effect yet. The
restorationScopeId enables the state restoration ability for descendant widgets. It also turns on the basic navigation stack history restoration.