RestorationManager class Null safety

Manages the restoration data in the framework and synchronizes it with the engine.

Restoration data can be serialized out and - at a later point in time - be used to restore the application to the previous state described by the serialized data. Mobile operating systems use the concept of state restoration to provide the illusion that apps continue to run in the background forever: after an app has been backgrounded, the user can always return to it and find it in the same state. In practice, the operating system may, however, terminate the app to free resources for other apps running in the foreground. Before that happens, the app gets a chance to serialize out its restoration data. When the user navigates back to the backgrounded app, it is restarted and the serialized restoration data is provided to it again. Ideally, the app will use that data to restore itself to the same state it was in when the user backgrounded the app.

In Flutter, restoration data is organized in a tree of RestorationBuckets which is rooted in the rootBucket. All information that the application needs to restore its current state must be stored in a bucket in this hierarchy. To store data in the hierarchy, entities (e.g. Widgets) must claim ownership of a child bucket from a parent bucket (which may be the rootBucket provided by this RestorationManager). The owner of a bucket may store arbitrary values in the bucket as long as they can be serialized with the StandardMessageCodec. The values are stored in the bucket under a given restoration ID as key. A restoration ID is a String that must be unique within a given bucket. To access the stored value again during state restoration, the same restoration ID must be provided again. The owner of the bucket may also make the bucket available to other entities so that they can claim child buckets from it for their own restoration needs. Within a bucket, child buckets are also identified by unique restoration IDs. The restoration ID must be provided when claiming a child bucket.

When restoration data is provided to the RestorationManager (e.g. after the application relaunched when foregrounded again), the bucket hierarchy with all the data stored in it is restored. Entities can retrieve the data again by using the same restoration IDs that they originally used to store the data.

In addition to providing restoration data when the app is launched, restoration data may also be provided to a running app to restore it to a previous state (e.g. when the user hits the back/forward button in the web browser). When this happens, the RestorationManager notifies its listeners (added via addListener) that a new rootBucket is available. In response to the notification, listeners must stop using the old bucket and restore their state from the information in the new rootBucket.

Same platforms restrict the size of the restoration data. Therefore, the data stored in the buckets should be as small as possible while still allowing the app to restore its current state from it. Data that can be retrieved from other services (e.g. a database or a web server) should not be included in the restoration data. Instead, a small identifier (e.g. a UUID, database record number, or resource locator) should be stored that can be used to retrieve the data again from its original source during state restoration.

The RestorationManager sends a serialized version of the bucket hierarchy over to the engine at the end of a frame in which the data in the hierarchy or its shape has changed. The engine caches the data until the operating system needs it. The application is responsible for keeping the data in the bucket always up-to-date to reflect its current state.

Discussion

Due to Flutter's threading model and restrictions in the APIs of the platforms Flutter runs on, restoration data must be stored in the buckets proactively as described above. When the operating system asks for the restoration data, it will do so on the platform thread expecting a synchronous response. To avoid the risk of deadlocks, the platform thread cannot block and call into the UI thread (where the dart code is running) to retrieve the restoration data. For this reason, the RestorationManager always sends the latest copy of the restoration data from the UI thread over to the platform thread whenever it changes. That way, the restoration data is always ready to go on the platform thread when the operating system needs it.

State Restoration on iOS

To enable state restoration on iOS, a restoration identifier has to be assigned to the FlutterViewController. If the standard embedding (produced by flutter create) is used, this can be accomplished with the following steps:

  1. In the app's directory, open ios/Runner.xcodeproj with Xcode.
  2. Select Main.storyboard under Runner/Runner in the Project Navigator on the left.
  3. Select the Flutter View Controller under Flutter View Controller Scene in the view hierarchy.
  4. Navigate to the Identity Inspector in the panel on the right.
  5. Enter a unique restoration ID in the provided field.
  6. Save the project.

Development with hot restart and hot reload

Changes applied to your app with hot reload and hot restart are not persisted on the device. They are lost when the app is fully terminated and restarted, e.g. by the operating system. Therefore, your app may not restore correctly during development if you have made changes and applied them with hot restart or hot reload. To test state restoration, always make sure to fully re-compile your application (e.g. by re-executing flutter run) after making a change.

Testing State Restoration

To test state restoration on Android:

  1. Turn on "Don't keep activities", which destroys the Android activity as soon as the user leaves it. This option should become available when Developer Options are turned on for the device.
  2. Run the code sample on an Android device.
  3. Create some in-memory state in the app on the phone, e.g. by navigating to a different screen.
  4. Background the Flutter app, then return to it. It will restart and restore its state.

To test state restoration on iOS:

  1. Open ios/Runner.xcworkspace/ in Xcode.
  2. (iOS 14+ only): Switch to build in profile or release mode, as launching an app from the home screen is not supported in debug mode.
  3. Press the Play button in Xcode to build and run the app.
  4. Create some in-memory state in the app on the phone, e.g. by navigating to a different screen.
  5. Background the app on the phone, e.g. by going back to the home screen.
  6. Press the Stop button in Xcode to terminate the app while running in the background.
  7. Open the app again on the phone (not via Xcode). It will restart and restore its state.

See also:

Inheritance
Implementers

Constructors

RestorationManager()
Construct the restoration manager and set up the communications channels with the engine to get restoration messages (by calling initChannels).

Properties

hashCode int
The hash code for this object. [...]
read-only, inherited
hasListeners bool
Whether any listeners are currently registered. [...]
@protected, read-only, inherited
isReplacing bool
Returns true for the frame after rootBucket has been replaced with a new non-null bucket. [...]
read-only
rootBucket Future<RestorationBucket?>
The root of the RestorationBucket hierarchy containing the restoration data. [...]
read-only
runtimeType Type
A representation of the runtime type of the object.
read-only, inherited

Methods

addListener(VoidCallback listener) → void
Register a closure to be called when the object changes. [...]
inherited
dispose() → void
Discards any resources used by the object. After this is called, the object is not in a usable state and should be discarded (calls to addListener and removeListener will throw after the object is disposed). [...]
@mustCallSuper, inherited
flushData() → void
Called to manually flush the restoration data to the engine. [...]
handleRestorationUpdateFromEngine({required bool enabled, required Uint8List? data}) → void
Called by the RestorationManager on itself to parse the restoration information obtained from the engine. [...]
initChannels() → void
Sets up the method call handler for SystemChannels.restoration. [...]
noSuchMethod(Invocation invocation) → dynamic
Invoked when a non-existent method or property is accessed. [...]
inherited
notifyListeners() → void
Call all the registered listeners. [...]
removeListener(VoidCallback listener) → void
Remove a previously registered closure from the list of closures that are notified when the object changes. [...]
inherited
scheduleSerializationFor(RestorationBucket bucket) → void
Called by a RestorationBucket to request serialization for that bucket. [...]
sendToEngine(Uint8List encodedData) Future<void>
Called by the RestorationManager on itself to send the provided encoded restoration data to the engine. [...]
toString() String
A string representation of this object. [...]
inherited
unscheduleSerializationFor(RestorationBucket bucket) → void
Called by a RestorationBucket to unschedule a request for serialization. [...]

Operators

operator ==(Object other) bool
The equality operator. [...]
inherited