Skip to content

[UIScene] Migrate state restoration for scenes #174402

@vashworth

Description

@vashworth

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:

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work listplatform-iosiOS applications specificallyteam-iosOwned by iOS platform teamtriaged-iosTriaged by iOS platform team

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions