Introduction to State Restoration in Flutter
Tags: flutter, dart, android, ios
Published: 2020-11-30 15:07:59

This is what we have from [Apple's documentation](https://developer.apple.com/documentation/uikit/view_controllers/preserving_your_app_s_ui_across_launches?language=objc):

>Preserving your app’s user interface helps maintain the illusion that your app is always running. Interruptions can occur frequently on iOS devices, and a prolonged interruption might cause the system to terminate your app to free up resources. However, users do not know that your app has been terminated and will not expect the state of your app to change. Instead, they expect your app to be in the same state as when they left it. State preservation and restoration ensures that your app returns to its previous state when it launches again.


Even though this comes from Apple, the same applies for Android apps as well. You can read the related Android documentation [here](https://developer.android.com/topic/libraries/architecture/saving-states). Before we procceed, note that this is all about the user experience and as developers, it is our responsability to implement and provide a good UX to our users. 

Ok, now that we have some knowleadge about what is SR and why it is important, let's se how it works in Flutter.


## What about Flutter?

Back in the days, this was a difficult task because we didn't have official support from the framework, basically we had to implement a cache strategy ourself, and this was not an easy task. See [issue #6827](https://github.com/flutter/flutter/issues/6827) on GitHub.

In Flutter `1.22`, we now have Framework support for state restoration out of the box and this comes to save us a lot of time.

Flutter give us an synchronous API for us to provide the data that represents the state of our UI to be saved every time it changes so that any time our app is destroyed, the engine already has the latest information of or UI to be saved into the OS and restored if the user comes back to our app.

### New concepts, same finality

If you come from Android (like me) or iOS, you will notice that the concepts are the same, we just have different ways of doing it.

Flutter hava the `RestorationManager`, which is the class that is responsible of handling all the state restoration work, usually we don't need to use it directly.  `RestorationBucket` which is used to hold the piece of the restoration data that our app need to restore its state later. `RestorationScope`, which is used to provide a scoped `RestorationBucket` to its descendants, if the `restorationId` parameter is null then, the restoration capability is disabled for its descedants. `RestorationMixin` this is used by our widget's state, he is the one we will usually use, it provides a straightfoward API to save and restore our state. And finally we have restorable properties (one for each Dart's data type), which are used to represent the data to be stored in the buckets.

## How to use it?

Flutter app widgets (`MaterialApp`, `CupertinoApp` and `WidgetsApp`) has a `RootRestorationScope` built-in by default so we don't need to provide one, instead we just need to provide an id that will be used by the inner root scope. Notice that without providing the restoration scope id, the state restoration feature will be turned off so make sure you don't miss this step.

One of the advantages of providing the restoration scope id into our app widget is that it also enables the Navigator built by the `WidgetsApp` to restore its state (i.e. to restore the history stack of active Routes). See the documentation on `Navigator` for more details around state restoration of Routes.

Let's see how we can use state restoration to save (and restore) the index of the `BottomNavigationBar`. Here is what we need to do:

1. Provide a `restorationScopeId` to our `MaterialApp` (`CupertinoApp` and `WidgetsApp` also have this parameter);
2. Our home page state should e mixed-in with `RestorationMixin` (and implement its members);
3. Create our restorable properties and use it instead of the common Dart's data type.
4. Resgister our restorable properties for restoration;

And that is it, Flutter will take care of the rest.

## Example
The code bellow will save the index of the bottom navigation bar if the app is killed.

```dart
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // Give your RootRestorationScope an id, defaults to null.
      restorationScopeId: 'root', 
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

// Our state should be mixed-in with RestorationMixin
class _HomePageState extends State<HomePage> with RestorationMixin {

  // For each state, we need to use a restorable property
  final RestorableInt _index = RestorableInt(0);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(child: Text('Index is ${_index.value}')),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _index.value,
        onTap: (i) => setState(() => _index.value = i),
        items: <BottomNavigationBarItem>[
          BottomNavigationBarItem(
              icon: Icon(Icons.home),
              label: 'Home'
          ),
          BottomNavigationBarItem(
              icon: Icon(Icons.notifications),
              label: 'Notifications'
          ),
          BottomNavigationBarItem(
              icon: Icon(Icons.settings),
              label: 'Settings'
          ),
        ],
      ),
    );
  }

  @override
  // The restoration bucket id for this page,
  // let's give it the name of our page!
  String get restorationId => 'home_page';

  @override
  void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
    // Register our property to be saved every time it changes,
    // and to be restored every time our app is killed by the OS!
    registerForRestoration(_index, 'nav_bar_index');
  }
}
```

### How to test?
If you want to test this code (or your code during development), you will need to enable the "Don't keep activities" options in your device's Developer Options. 

1. Open the Developer options settings, and enable the option `Don't keep activities`.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/1o7w89210qxdwtuvbw31.jpeg)

This will simulate the lack of memory and the device will kill all activities as soon as you leave it.

2. Run the code above app and tap the `Settings` item;
3. Go to the launcher by pressing the device's home button.
Press the overview button and return to the app.
4. notice that the `Settings` item is still selected.

![With State Restoration](https://dev-to-uploads.s3.amazonaws.com/i/qnh23kra7o5mkgpkr4j4.gif)

**Note:** You can try the same app without state restoration (find the code [here](https://gist.github.com/pedromassango/014501d092a15899b2bc27c28577334f)), where if you run it and repeat the same steps you will notice that the index is always back to `Home`once you launch the app again. Make sure you stop and run the code code again because state restoration code is not removed with hot-reload/restart as it is tied to the host OS!

![Without State Restoration](https://dev-to-uploads.s3.amazonaws.com/i/84a3tjj973b6guxixgzg.gif)

That is it for today. There is still a lot of things to talk about state restoration and I will split into more than one article so make sure you follow me (here or on Twitter) to not miss the next one.