Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

State Restoration for iOS #23495

Merged
merged 15 commits into from
Jan 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -993,6 +993,9 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatfor
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegateTest.m
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate_internal.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterRestorationPlugin.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterRestorationPlugin.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterRestorationPluginTest.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm
Expand Down
3 changes: 3 additions & 0 deletions shell/platform/darwin/ios/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ source_set("flutter_framework_source") {
"framework/Source/FlutterPlatformViews_Internal.h",
"framework/Source/FlutterPlatformViews_Internal.mm",
"framework/Source/FlutterPluginAppLifeCycleDelegate.mm",
"framework/Source/FlutterRestorationPlugin.h",
"framework/Source/FlutterRestorationPlugin.mm",
"framework/Source/FlutterTextInputDelegate.h",
"framework/Source/FlutterTextInputPlugin.h",
"framework/Source/FlutterTextInputPlugin.mm",
Expand Down Expand Up @@ -221,6 +223,7 @@ shared_library("ios_test_flutter") {
"framework/Source/FlutterEngineGroupTest.mm",
"framework/Source/FlutterEngineTest.mm",
"framework/Source/FlutterPluginAppLifeCycleDelegateTest.m",
"framework/Source/FlutterRestorationPluginTest.mm",
"framework/Source/FlutterTextInputPluginTest.m",
"framework/Source/FlutterViewControllerTest.mm",
"framework/Source/SemanticsObjectTest.mm",
Expand Down
36 changes: 35 additions & 1 deletion shell/platform/darwin/ios/framework/Headers/FlutterEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,31 @@ FLUTTER_EXPORT
*/
- (instancetype)initWithName:(NSString*)labelPrefix
project:(nullable FlutterDartProject*)project
allowHeadlessExecution:(BOOL)allowHeadlessExecution NS_DESIGNATED_INITIALIZER;
allowHeadlessExecution:(BOOL)allowHeadlessExecution;

/**
* Initialize this FlutterEngine with a `FlutterDartProject`.
*
* If the FlutterDartProject is not specified, the FlutterEngine will attempt to locate
* the project in a default location (the flutter_assets folder in the iOS application
* bundle).
*
* A newly initialized engine will not run the `FlutterDartProject` until either
* `-runWithEntrypoint:` or `-runWithEntrypoint:libraryURI:` is called.
*
* @param labelPrefix The label prefix used to identify threads for this instance. Should
* be unique across FlutterEngine instances, and is used in instrumentation to label
* the threads used by this FlutterEngine.
* @param project The `FlutterDartProject` to run.
* @param allowHeadlessExecution Whether or not to allow this instance to continue
* running after passing a nil `FlutterViewController` to `-setViewController:`.
* @param restorationEnabled Whether state restoration is enabled. When true, the framework will
* wait for the attached view controller to provide restoration data.
*/
- (instancetype)initWithName:(NSString*)labelPrefix
project:(nullable FlutterDartProject*)project
allowHeadlessExecution:(BOOL)allowHeadlessExecution
restorationEnabled:(BOOL)restorationEnabled NS_DESIGNATED_INITIALIZER;

+ (instancetype)new NS_UNAVAILABLE;

Expand Down Expand Up @@ -273,6 +297,16 @@ FLUTTER_EXPORT
*/
@property(nonatomic, readonly) FlutterMethodChannel* navigationChannel;

/**
* The `FlutterMethodChannel` used for restoration related platform messages.
*
* Can be nil after `destroyContext` is called.
*
* @see [Restoration
* Channel](https://api.flutter.dev/flutter/services/SystemChannels/restoration-constant.html)
*/
@property(nonatomic, readonly) FlutterMethodChannel* restorationChannel;

/**
* The `FlutterMethodChannel` used for core platform messages, such as
* information about the screen orientation.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ FLUTTER_DEPRECATED("FlutterEngine should be used rather than FlutterHeadlessDart
@interface FlutterHeadlessDartRunner : FlutterEngine

/**
* Iniitalize this FlutterHeadlessDartRunner with a `FlutterDartProject`.
* Initialize this FlutterHeadlessDartRunner with a `FlutterDartProject`.
*
* If the FlutterDartProject is not specified, the FlutterHeadlessDartRunner will attempt to locate
* the project in a default location.
Expand All @@ -49,7 +49,7 @@ FLUTTER_DEPRECATED("FlutterEngine should be used rather than FlutterHeadlessDart
- (instancetype)initWithName:(NSString*)labelPrefix project:(FlutterDartProject*)projectOrNil;

/**
* Iniitalize this FlutterHeadlessDartRunner with a `FlutterDartProject`.
* Initialize this FlutterHeadlessDartRunner with a `FlutterDartProject`.
*
* If the FlutterDartProject is not specified, the FlutterHeadlessDartRunner will attempt to locate
* the project in a default location.
Expand All @@ -64,7 +64,27 @@ FLUTTER_DEPRECATED("FlutterEngine should be used rather than FlutterHeadlessDart
*/
- (instancetype)initWithName:(NSString*)labelPrefix
project:(FlutterDartProject*)projectOrNil
allowHeadlessExecution:(BOOL)allowHeadlessExecution NS_DESIGNATED_INITIALIZER;
allowHeadlessExecution:(BOOL)allowHeadlessExecution;

/**
* Initialize this FlutterHeadlessDartRunner with a `FlutterDartProject`.
*
* If the FlutterDartProject is not specified, the FlutterHeadlessDartRunner will attempt to locate
* the project in a default location.
*
* A newly initialized engine will not run the `FlutterDartProject` until either
* `-runWithEntrypoint:` or `-runWithEntrypoint:libraryURI` is called.
*
* @param labelPrefix The label prefix used to identify threads for this instance. Should
* be unique across FlutterEngine instances
* @param projectOrNil The `FlutterDartProject` to run.
* @param allowHeadlessExecution Must be set to `YES`.
* @param restorationEnabled Must be set to `NO`.
*/
- (instancetype)initWithName:(NSString*)labelPrefix
project:(FlutterDartProject*)projectOrNil
allowHeadlessExecution:(BOOL)allowHeadlessExecution
restorationEnabled:(BOOL)restorationEnabled NS_DESIGNATED_INITIALIZER;

/**
* Not recommended for use - will initialize with a default label ("io.flutter.headless")
Expand Down
40 changes: 37 additions & 3 deletions shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate_internal.h"

static NSString* kUIBackgroundMode = @"UIBackgroundModes";
static NSString* kRemoteNotificationCapabitiliy = @"remote-notification";
static NSString* kBackgroundFetchCapatibility = @"fetch";
static NSString* const kUIBackgroundMode = @"UIBackgroundModes";
static NSString* const kRemoteNotificationCapabitiliy = @"remote-notification";
static NSString* const kBackgroundFetchCapatibility = @"fetch";
static NSString* const kRestorationStateAppModificationKey = @"mod-date";

@interface FlutterAppDelegate ()
@property(nonatomic, copy) FlutterViewController* (^rootFlutterViewControllerGetter)(void);
Expand Down Expand Up @@ -308,4 +309,37 @@ - (void)logCapabilityConfigurationWarningIfNeeded:(SEL)selector {
}
}

#pragma mark - State Restoration

- (BOOL)application:(UIApplication*)application shouldSaveApplicationState:(NSCoder*)coder {
[coder encodeInt64:self.lastAppModificationTime forKey:kRestorationStateAppModificationKey];
return YES;
}

- (BOOL)application:(UIApplication*)application shouldRestoreApplicationState:(NSCoder*)coder {
int64_t stateDate = [coder decodeInt64ForKey:kRestorationStateAppModificationKey];
return self.lastAppModificationTime == stateDate;
}

- (BOOL)application:(UIApplication*)application shouldSaveSecureApplicationState:(NSCoder*)coder {
[coder encodeInt64:self.lastAppModificationTime forKey:kRestorationStateAppModificationKey];
return YES;
}

- (BOOL)application:(UIApplication*)application
shouldRestoreSecureApplicationState:(NSCoder*)coder {
int64_t stateDate = [coder decodeInt64ForKey:kRestorationStateAppModificationKey];
return self.lastAppModificationTime == stateDate;
}

- (int64_t)lastAppModificationTime {
NSDate* fileDate;
NSError* error = nil;
[[[NSBundle mainBundle] executableURL] getResourceValue:&fileDate
forKey:NSURLContentModificationDateKey
error:&error];
NSAssert(error == nil, @"Cannot obtain modification date of main bundle: %@", error);
return [fileDate timeIntervalSince1970];
}

@end
30 changes: 30 additions & 0 deletions shell/platform/darwin/ios/framework/Source/FlutterEngine.mm
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,10 @@ @implementation FlutterEngine {
// Channels
fml::scoped_nsobject<FlutterPlatformPlugin> _platformPlugin;
fml::scoped_nsobject<FlutterTextInputPlugin> _textInputPlugin;
fml::scoped_nsobject<FlutterRestorationPlugin> _restorationPlugin;
fml::scoped_nsobject<FlutterMethodChannel> _localizationChannel;
fml::scoped_nsobject<FlutterMethodChannel> _navigationChannel;
fml::scoped_nsobject<FlutterMethodChannel> _restorationChannel;
fml::scoped_nsobject<FlutterMethodChannel> _platformChannel;
fml::scoped_nsobject<FlutterMethodChannel> _platformViewsChannel;
fml::scoped_nsobject<FlutterMethodChannel> _textInputChannel;
Expand All @@ -84,6 +86,7 @@ @implementation FlutterEngine {
int64_t _nextTextureId;

BOOL _allowHeadlessExecution;
BOOL _restorationEnabled;
FlutterBinaryMessengerRelay* _binaryMessenger;
std::unique_ptr<flutter::ConnectionCollection> _connections;
}
Expand All @@ -103,10 +106,21 @@ - (instancetype)initWithName:(NSString*)labelPrefix project:(FlutterDartProject*
- (instancetype)initWithName:(NSString*)labelPrefix
project:(FlutterDartProject*)project
allowHeadlessExecution:(BOOL)allowHeadlessExecution {
return [self initWithName:labelPrefix
project:project
allowHeadlessExecution:allowHeadlessExecution
restorationEnabled:NO];
}

- (instancetype)initWithName:(NSString*)labelPrefix
project:(FlutterDartProject*)project
allowHeadlessExecution:(BOOL)allowHeadlessExecution
restorationEnabled:(BOOL)restorationEnabled {
self = [super init];
NSAssert(self, @"Super init cannot be nil");
NSAssert(labelPrefix, @"labelPrefix is required");

_restorationEnabled = restorationEnabled;
_allowHeadlessExecution = allowHeadlessExecution;
_labelPrefix = [labelPrefix copy];

Expand Down Expand Up @@ -331,12 +345,18 @@ - (FlutterPlatformPlugin*)platformPlugin {
- (FlutterTextInputPlugin*)textInputPlugin {
return _textInputPlugin.get();
}
- (FlutterRestorationPlugin*)restorationPlugin {
return _restorationPlugin.get();
}
- (FlutterMethodChannel*)localizationChannel {
return _localizationChannel.get();
}
- (FlutterMethodChannel*)navigationChannel {
return _navigationChannel.get();
}
- (FlutterMethodChannel*)restorationChannel {
return _restorationChannel.get();
}
- (FlutterMethodChannel*)platformChannel {
return _platformChannel.get();
}
Expand All @@ -363,6 +383,7 @@ - (NSURL*)observatoryUrl {
- (void)resetChannels {
_localizationChannel.reset();
_navigationChannel.reset();
_restorationChannel.reset();
_platformChannel.reset();
_platformViewsChannel.reset();
_textInputChannel.reset();
Expand Down Expand Up @@ -413,6 +434,11 @@ - (void)setupChannels {
_initialRoute = nil;
}

_restorationChannel.reset([[FlutterMethodChannel alloc]
initWithName:@"flutter/restoration"
binaryMessenger:self.binaryMessenger
codec:[FlutterStandardMethodCodec sharedInstance]]);

_platformChannel.reset([[FlutterMethodChannel alloc]
initWithName:@"flutter/platform"
binaryMessenger:self.binaryMessenger
Expand Down Expand Up @@ -452,6 +478,10 @@ - (void)setupChannels {
_textInputPlugin.get().textInputDelegate = self;

_platformPlugin.reset([[FlutterPlatformPlugin alloc] initWithEngine:[self getWeakPtr]]);

_restorationPlugin.reset([[FlutterRestorationPlugin alloc]
initWithChannel:_restorationChannel.get()
restorationEnabled:_restorationEnabled]);
}

- (void)maybeSetupPlatformViewChannels {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject_Internal.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterRestorationPlugin.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h"
#import "flutter/shell/platform/darwin/ios/platform_view_ios.h"
Expand All @@ -42,6 +43,7 @@ extern NSString* const FlutterEngineWillDealloc;
- (FlutterPlatformPlugin*)platformPlugin;
- (std::shared_ptr<flutter::FlutterPlatformViewsController>&)platformViewsController;
- (FlutterTextInputPlugin*)textInputPlugin;
- (FlutterRestorationPlugin*)restorationPlugin;
- (void)launchEngine:(NSString*)entrypoint libraryURI:(NSString*)libraryOrNil;
- (BOOL)createShell:(NSString*)entrypoint
libraryURI:(NSString*)libraryOrNil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,24 @@ - (instancetype)initWithName:(NSString*)labelPrefix
allowHeadlessExecution:(BOOL)allowHeadlessExecution {
NSAssert(allowHeadlessExecution == YES,
@"Cannot initialize a FlutterHeadlessDartRunner without headless execution.");
return [self initWithName:labelPrefix
project:projectOrNil
allowHeadlessExecution:allowHeadlessExecution
restorationEnabled:NO];
}

- (instancetype)initWithName:(NSString*)labelPrefix
project:(FlutterDartProject*)projectOrNil
allowHeadlessExecution:(BOOL)allowHeadlessExecution
restorationEnabled:(BOOL)restorationEnabled {
NSAssert(allowHeadlessExecution == YES,
@"Cannot initialize a FlutterHeadlessDartRunner without headless execution.");
return [super initWithName:labelPrefix
project:projectOrNil
allowHeadlessExecution:allowHeadlessExecution];
allowHeadlessExecution:allowHeadlessExecution
restorationEnabled:restorationEnabled];
}

- (instancetype)init {
return [self initWithName:@"io.flutter.headless" project:nil];
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERRESTORATIONPLUGIN_H_
#define SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERRESTORATIONPLUGIN_H_

#import <UIKit/UIKit.h>

#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h"

@interface FlutterRestorationPlugin : NSObject
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
- (instancetype)initWithChannel:(FlutterMethodChannel*)channel
restorationEnabled:(BOOL)waitForData NS_DESIGNATED_INITIALIZER;

@property(nonatomic, strong) NSData* restorationData;
- (void)markRestorationComplete;
- (void)reset;
@end
#endif // SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERRESTORATIONPLUGIN_H_
Loading