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.
Some 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:
- In the app's directory, open
ios/Runner.xcodeproj
with Xcode. - Select
Main.storyboard
underRunner/Runner
in the Project Navigator on the left. - Select the
Flutter View Controller
underFlutter View Controller Scene
in the view hierarchy. - Navigate to the Identity Inspector in the panel on the right.
- Enter a unique restoration ID in the provided field.
- 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:
- 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.
- Run the code sample on an Android device.
- Create some in-memory state in the app on the phone, e.g. by navigating to a different screen.
- Background the Flutter app, then return to it. It will restart and restore its state.
To test state restoration on iOS:
- Open
ios/Runner.xcworkspace/
in Xcode. - (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.
- Press the Play button in Xcode to build and run the app.
- Create some in-memory state in the app on the phone, e.g. by navigating to a different screen.
- Background the app on the phone, e.g. by going back to the home screen.
- Press the Stop button in Xcode to terminate the app while running in the background.
- Open the app again on the phone (not via Xcode). It will restart and restore its state.
See also:
- ServicesBinding.restorationManager, which holds the singleton instance of the RestorationManager for the currently running application.
- RestorationBucket, which make up the restoration data hierarchy.
- RestorationMixin, which uses RestorationBuckets behind the scenes to make State objects of StatefulWidgets restorable.
- Inheritance
-
- Object
- ChangeNotifier
- RestorationManager
- 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-onlyinherited
- hasListeners → bool
- Whether any listeners are currently registered.
- 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-onlyinherited
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 will throw after the object is disposed).
mustCallSuper">@mustCallSuperinherited
-
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.
protected">@protected
-
initChannels(
) → void -
Sets up the method call handler for SystemChannels.restoration.
protected">@protected
-
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.
protected">@protectedvisibleForTesting">@visibleForTesting
-
sendToEngine(
Uint8List encodedData) → Future< void> -
Called by the RestorationManager on itself to send the provided
encoded restoration data to the engine.
protected">@protected
-
toString(
) → String -
A string representation of this object.
inherited
-
unscheduleSerializationFor(
RestorationBucket bucket) → void -
Called by a RestorationBucket to unschedule a request for serialization.
protected">@protectedvisibleForTesting">@visibleForTesting
Operators
-
operator ==(
Object other) → bool -
The equality operator.
inherited