-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Description
Problem
After migrating to UIScene, state restoration no longer works the same as before.
Previously state restoration was ViewController-based. FlutterViewController methods encodeRestorableStateWithCoder and decodeRestorableStateWithCoder were used to save and restore state. application:shouldRestoreSecureApplicationState: in the FlutterAppDelegate also determined whether or not restoration should be allowed.
After migrating to UIScene, these methods are no longer called by UIKit. This does not appear to be documented anywhere.
Instead, state must be restored through the SceneDelegate.
Prerequisites
Proposal
In the FlutterSceneDelegate, there are two functions we can utilize for scene restoration:
- stateRestorationActivityForScene
- This is where you set the state to be stored
- restoreInteractionStateWithUserActivity
- This is where you can restore the state
Flutter uses a plugin called the restorationPlugin to send the restoration data to and from the framework. restorationPlugin is a property of the FlutterEngine. To access this plugin, the UIScene needs access to the FlutterEngine.
In addition, you need a way to associate a UIScene with a particular FlutterViewController/FlutterEngine when restoring. To do that, we can use the FlutterViewController’s restorationId, which persists across launches.
Lastly, we’ll need to store the last modified date of the app bundle to determine when and when not to restore the state (replacing application:shouldRestoreSecureApplicationState:).
- (NSUserActivity*)stateRestorationActivityForScene:(UIScene*)scene {
// Saves activity to the state
NSUserActivity* activity = scene.userActivity;
if (!activity) {
activity = [[NSUserActivity alloc] initWithActivityType:scene.session.configuration.name];
}
for (FlutterEngine* engine in [_engines allObjects]) {
if (!engine) {
continue;
}
UIViewController* vc = (UIViewController*)engine.viewController;
NSString* restorationId = vc.restorationIdentifier;
if (restorationId && restorationId.length > 0) {
NSData* restorationData = [engine.restorationPlugin restorationData];
if (restorationData) {
int64_t stateDate = [self lastAppModificationTime];
[activity addUserInfoEntriesFromDictionary:@{restorationId : restorationData}];
[activity addUserInfoEntriesFromDictionary:@{
kRestorationStateAppModificationKey : [NSNumber numberWithLongLong:stateDate]
}];
}
}
}
return activity;
}
- (void)scene:(UIScene*)scene
restoreInteractionStateWithUserActivity:(NSUserActivity*)stateRestorationActivity {
// Restores state from the activity
NSDictionary<NSString*, id>* userInfo = stateRestorationActivity.userInfo;
for (FlutterEngine* engine in [_engines allObjects]) {
if (!engine) {
continue;
}
UIViewController* vc = (UIViewController*)engine.viewController;
NSString* restorationId = vc.restorationIdentifier;
if (restorationId && restorationId.length > 0) {
NSNumber* stateDateNumber = userInfo[kRestorationStateAppModificationKey];
int64_t stateDate = 0;
if (stateDateNumber && [stateDateNumber isKindOfClass:[NSNumber class]]) {
stateDate = [stateDateNumber longLongValue];
}
if (self.lastAppModificationTime != stateDate) {
// Don't restore state if the app has been re-installed since the state was last saved
return;
}
NSData* restorationData = userInfo[restorationId];
if ([restorationData isKindOfClass:[NSData class]]) {
[engine.restorationPlugin setRestorationData:restorationData];
}
}
}
}See prototype.