Why do we need to manage state when the app is killed?
Imagine a user is filling out a long form in your mobile app. During this process, they need to refer to another document saved on their phone to complete a specific field. What happens when this user minimizes your app to access the document? You might expect that when they return, the app will resume right where they left off. But what if the operating system, running low on memory, decides to terminate your app in the background?
Suddenly, all of that user's progress is lost. They’ll have to start from scratch. This frustrating experience can drive users away, leading to higher drop-off rates and lower user retention. As an engineer, it's crucial to anticipate and handle such edge cases, ensuring that your app manages state efficiently, even when faced with OS-imposed limitations.
User Expectations and Performance Impact
It’s not just about user satisfaction; performance issues directly affect your app’s retention. Studies show that 53% of mobile users will leave an app or website if it takes longer than three seconds to load. Each additional 100 milliseconds of delay can cause users to abandon your app, and poor state management can add significant delays when users re-open your app and are forced to restart their tasks.
Consider this: Amazon reportedly gained 1% additional revenue for every 100ms improvement in page load speed. That's the kind of difference small optimizations, like preserving state, can make in user retention and business performance.
---
This real-world impact highlights how critical state management is, not just in terms of user experience, but also in driving engagement and revenue. Let’s explore how you can use Flutter’s RestorationMixin to solve this issue in your apps.
What is State Restoration?
State restoration refers to the process of saving and restoring the UI state of an application when it is terminated or removed from memory by the operating system. This is particularly important for mobile apps that deal with multi-step forms, media playback, or navigation flows where users expect their progress to persist across sessions.
Why Is It Important?
Mobile operating systems, like Android and iOS, often kill background apps to free up memory. When users reopen these apps, they expect to pick up where they left off, whether it's resuming a video, continuing to fill out a form, or seeing their last navigation position. Without state restoration, users would be forced to start over, leading to a frustrating experience.
How State Restoration Works in Flutter
Flutter introduced state restoration as part of its framework in version 2.0, making it easier to save and restore the UI state of your apps. By using the `RestorationMixin`, Flutter developers can efficiently restore a widget’s state after the app has been closed.
The basic idea is to store the state of your app in `RestorableProperties`, which the framework can save and restore as needed.
Key Classes and Concepts:
1. `RestorationMixin`: A mixin that adds state restoration capabilities to a widget.
2. `RestorableProperty`: Special properties that store values and are restorable, such as `RestorableInt`, `RestorableDouble`, and more.
3. `RestorationId`: A unique identifier for each widget that requires state restoration.
---
When Do You Need State Restoration?
State restoration is useful in a wide range of scenarios, such as:
- Form-based applications: Preserving user input across form pages, especially in multi-step forms.
- Media applications: Resuming a video or music playback from where the user left off.
- Complex navigation: Saving the user’s navigation history and restoring it after relaunch.
Getting Started with `RestorationMixin`
Let’s take a look at how you can implement state restoration in a simple Flutter application with a counter.
Basic Example: Counter App with State Restoration
Here’s a simple example of a counter app where the counter value is restored after the app is killed and relaunched:
import 'package:flutter/material.dart';
class CounterApp extends StatefulWidget {
@override
CounterAppState createState() => CounterAppState();
}
class _CounterAppState extends State<CounterApp> with RestorationMixin {
// Restorable property for saving and restoring the counter value
final RestorableInt _counter = RestorableInt(0);
@override
String? get restorationId => 'counter_app';
// Method that restores the state when the widget is recreated
@override
void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
registerForRestoration(_counter, 'counter');
}
@override
void dispose() {
_counter.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Restorable Counter'),
),
body: Center(
child: Text('Counter: ${_counter.value}'),
),
floatingActionButton: FloatingActionButton(
onPressed: () => setState(() {
_counter.value++;
}),
child: Icon(Icons.add),
),
);
}
}
void main() => runApp(MaterialApp(
restorationScopeId: 'root',
home: CounterApp(),
));
Get the complete code here: https://docs.flutter.dev/development/ui/advanced/restoration

Flutter RestorationMixing State Management Example App Demo
Explanation of the Code
- `RestorableInt`: A special property that allows the integer state (the counter) to be saved and restored across sessions.
- `restorationId`: A unique identifier (`counter_app`) that allows Flutter to identify the widget and restore its state.
- `restoreState`: This method is called whenever the widget is restored after termination. In this case, the counter value is registered for restoration using `registerForRestoration()`.
- `restorationScopeId`: This ID is assigned at the app level to scope the state restoration, ensuring all widgets inside this app scope participate in restoration.
When you run this app and increment the counter, you can kill the app, relaunch it, and see that the counter value is restored.
Advanced Use Cases for State Restoration
State restoration is not limited to simple use cases like counters. It can be applied in more complex scenarios such as:
- Form input persistence: Save form data entered by the user in each field.
- Navigation restoration: Maintain the navigation stack so users return to the same screen they left.
- Media apps: Save the current playback position of a video or audio file.
For example, you can use multiple `RestorableProperties` to restore the state of a form, tabs, or media controls. Widgets like `TabController` and `TextField` can be customized to save and restore their values using `RestorableDouble` or `RestorableString`.
Can RestorationMixin be used when the user closes the app?
The `RestorationStateMixin` in Flutter is designed to help with state restoration when your app is killed and then resumed, or when its process is killed and recreated by the system (for example, when using the "Don't keep activities" setting on Android). However, this mechanism does not persist state across complete app terminations, such as when you swipe the app from the recent apps menu or restart the device. This behavior is expected as swiping an app away from the switcher menu is treated as a "hard kill," which does not trigger state restoration in the same way as when the system temporarily removes the app from memory.
> With the RestorationManager, the Flutter framework provides the state data to the engine as the state changes, so that the app is ready when the OS signals that it's about to kill the app, giving the app only moments to prepare.
See: [Restore state on Android](https://docs.flutter.dev/platform-integration/android/restore-state-android#overview)
Why State Is Not Restored After Swiping the App:
When you swipe an app away from the app switcher, the OS terminates the app entirely. This does not give the app a chance to save any state. State restoration mechanisms like `RestorationStateMixin` are for restoring UI state after a system-initiated process death, not after a user-initiated termination.
Persisting State Across Terminations:
To persist state across such app terminations, you need to store the state in persistent storage, such as shared preferences, local databases (e.g., `sqflite`), or a cloud backend. You can save the state in these storages in your app’s lifecycle events (such as `AppLifecycleState.paused`) and reload it when the app starts again.
Here's an example using `SharedPreferences` to persist the counter value:
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
runApp(const MaterialApp(home: MyPersistentApp()));
}
class MyPersistentApp extends StatefulWidget {
@override
MyPersistentAppState createState() => MyPersistentAppState();
}
class _MyPersistentAppState extends State<MyPersistentApp> {
int _counter = 0;
@override
void initState() {
super.initState();
_loadCounter();
}
_loadCounter() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
_counter = (prefs.getInt('counter') ?? 0);
});
}
_incrementCounter() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
_counter++;
});
prefs.setInt('counter', _counter);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Persistent Counter'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('You have pushed the button this many times:'),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
Best Practices for State Restoration in Flutter
Here are some best practices when working with state restoration:
1. Optimize Performance: Avoid saving large objects or states that aren’t critical to the user experience, as this can lead to performance overhead.
2. Use `RestorationMixin` Wisely: Only apply state restoration to widgets that truly need it. Overusing it can lead to unnecessary complexity.
3. Test Edge Cases: Ensure that the restoration works smoothly when the app is updated or when there’s a system-level restore event.
4. Restore Navigation History: For apps with deep navigation flows, use the restoration mixin to persist and restore the navigation stack.
In Conclusion
Ensuring a seamless user experience goes beyond just building a functional app. It requires thoughtful handling of UX cases like state restoration, which can have a significant impact on user satisfaction and retention. If you want to make sure that your app has these critical UX aspects covered, and that seemingly small issues—like losing state—don’t lead to a major drop in user engagement, we're here to help.
At Multiverse Software, we specialize in delivering high-quality, user-focused mobile applications. Let’s make sure your app is equipped to handle these challenges and more. Get in touch with us to take your app to the next level!
Additional Resources
- [Flutter Documentation: State Restoration](https://docs.flutter.dev/development/ui/advanced/restoration)
- [RestorationMixin API Reference](https://api.flutter.dev/flutter/widgets/RestorationMixin-class.html)
- [Flutter State Restoration Sample Code](https://github.com/praharshbhatt/flutter_restorationmixin_example)