State Restoration for iOS (flutter/engine#23495)

This commit is contained in:
Michael Goderbauer 2021-01-14 15:54:04 -08:00 committed by GitHub
parent 8f6cd9f6e8
commit ea1b8adee8
14 changed files with 492 additions and 9 deletions

View File

@ -990,6 +990,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

View File

@ -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",
@ -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",

View File

@ -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;
@ -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.

View File

@ -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.
@ -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.
@ -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")

View File

@ -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);
@ -308,4 +309,37 @@ static BOOL IsDeepLinkingEnabled(NSDictionary* infoDictionary) {
}
}
#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

View File

@ -71,8 +71,10 @@ static constexpr int kNumProfilerSamplesPerSec = 5;
// 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;
@ -84,6 +86,7 @@ static constexpr int kNumProfilerSamplesPerSec = 5;
int64_t _nextTextureId;
BOOL _allowHeadlessExecution;
BOOL _restorationEnabled;
FlutterBinaryMessengerRelay* _binaryMessenger;
std::unique_ptr<flutter::ConnectionCollection> _connections;
}
@ -103,10 +106,21 @@ static constexpr int kNumProfilerSamplesPerSec = 5;
- (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];
@ -331,12 +345,18 @@ static constexpr int kNumProfilerSamplesPerSec = 5;
- (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();
}
@ -363,6 +383,7 @@ static constexpr int kNumProfilerSamplesPerSec = 5;
- (void)resetChannels {
_localizationChannel.reset();
_navigationChannel.reset();
_restorationChannel.reset();
_platformChannel.reset();
_platformViewsChannel.reset();
_textInputChannel.reset();
@ -413,6 +434,11 @@ static constexpr int kNumProfilerSamplesPerSec = 5;
_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
@ -452,6 +478,10 @@ static constexpr int kNumProfilerSamplesPerSec = 5;
_textInputPlugin.get().textInputDelegate = self;
_platformPlugin.reset([[FlutterPlatformPlugin alloc] initWithEngine:[self getWeakPtr]]);
_restorationPlugin.reset([[FlutterRestorationPlugin alloc]
initWithChannel:_restorationChannel.get()
restorationEnabled:_restorationEnabled]);
}
- (void)maybeSetupPlatformViewChannels {

View File

@ -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"
@ -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

View File

@ -35,10 +35,24 @@
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];
}

View File

@ -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_

View File

@ -0,0 +1,107 @@
// 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.
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterRestorationPlugin.h"
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#include "flutter/fml/logging.h"
FLUTTER_ASSERT_NOT_ARC
@interface FlutterRestorationPlugin ()
@property(nonatomic, copy) FlutterResult pendingRequest;
@end
@implementation FlutterRestorationPlugin {
BOOL _waitForData;
BOOL _restorationEnabled;
}
- (instancetype)init {
@throw([NSException
exceptionWithName:@"FlutterRestorationPlugin must initWithChannel:restorationEnabled:"
reason:nil
userInfo:nil]);
}
- (instancetype)initWithChannel:(FlutterMethodChannel*)channel
restorationEnabled:(BOOL)restorationEnabled {
FML_DCHECK(channel) << "channel must be set";
self = [super init];
if (self) {
[channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
[self handleMethodCall:call result:result];
}];
_restorationEnabled = restorationEnabled;
_waitForData = restorationEnabled;
}
return self;
}
- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
if ([[call method] isEqualToString:@"put"]) {
NSAssert(self.pendingRequest == nil, @"Cannot put data while a get request is pending.");
FlutterStandardTypedData* data = [call arguments];
self.restorationData = [data data];
result(nil);
} else if ([[call method] isEqualToString:@"get"]) {
if (!_restorationEnabled || !_waitForData) {
result([self dataForFramework]);
return;
}
NSAssert(self.pendingRequest == nil, @"There can only be one pending request.");
self.pendingRequest = result;
} else {
result(FlutterMethodNotImplemented);
}
}
- (void)setRestorationData:(NSData*)data {
if (data != _restorationData) {
[_restorationData release];
_restorationData = [data retain];
}
_waitForData = NO;
if (self.pendingRequest != nil) {
self.pendingRequest([self dataForFramework]);
self.pendingRequest = nil;
}
}
- (void)markRestorationComplete {
_waitForData = NO;
if (self.pendingRequest != nil) {
NSAssert(_restorationEnabled, @"No request can be pending when restoration is disabled.");
self.pendingRequest([self dataForFramework]);
self.pendingRequest = nil;
}
}
- (void)reset {
self.pendingRequest = nil;
self.restorationData = nil;
}
- (NSDictionary*)dataForFramework {
if (!_restorationEnabled) {
return @{@"enabled" : @NO};
}
if (self.restorationData == nil) {
return @{@"enabled" : @YES};
}
return @{
@"enabled" : @YES,
@"data" : [FlutterStandardTypedData typedDataWithBytes:self.restorationData]
};
}
- (void)dealloc {
[_restorationData release];
[_pendingRequest release];
[super dealloc];
}
@end

View File

@ -0,0 +1,193 @@
// 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.
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterRestorationPlugin.h"
#import <OCMock/OCMock.h>
#import <XCTest/XCTest.h>
#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h"
#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h"
FLUTTER_ASSERT_ARC
@interface FlutterRestorationPlugin ()
- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result;
@end
@interface FlutterRestorationPluginTest : XCTestCase
@end
@implementation FlutterRestorationPluginTest {
id restorationChannel;
}
- (void)setUp {
[super setUp];
restorationChannel = OCMClassMock([FlutterMethodChannel class]);
}
- (void)tearDown {
[restorationChannel stopMocking];
[super tearDown];
}
#pragma mark - Tests
- (void)testRestorationEnabledWaitsForData {
FlutterRestorationPlugin* restorationPlugin =
[[FlutterRestorationPlugin alloc] initWithChannel:restorationChannel restorationEnabled:YES];
FlutterMethodCall* methodCall = [FlutterMethodCall methodCallWithMethodName:@"get" arguments:nil];
__block id capturedResult;
[restorationPlugin handleMethodCall:methodCall
result:^(id _Nullable result) {
capturedResult = result;
}];
XCTAssertNil(capturedResult);
NSData* data = [@"testrestortiondata" dataUsingEncoding:NSUTF8StringEncoding];
[restorationPlugin setRestorationData:data];
XCTAssertEqual([capturedResult count], 2u);
XCTAssertEqual([capturedResult objectForKey:@"enabled"], @YES);
XCTAssertEqual([[capturedResult objectForKey:@"data"] data], data);
}
- (void)testRestorationDisabledRespondsRightAway {
FlutterRestorationPlugin* restorationPlugin =
[[FlutterRestorationPlugin alloc] initWithChannel:restorationChannel restorationEnabled:NO];
FlutterMethodCall* methodCall = [FlutterMethodCall methodCallWithMethodName:@"get" arguments:nil];
__block id capturedResult;
[restorationPlugin handleMethodCall:methodCall
result:^(id _Nullable result) {
capturedResult = result;
}];
XCTAssertEqual([capturedResult count], 1u);
XCTAssertEqual([capturedResult objectForKey:@"enabled"], @NO);
}
- (void)testRespondsRightAwayWhenDataIsSet {
FlutterRestorationPlugin* restorationPlugin =
[[FlutterRestorationPlugin alloc] initWithChannel:restorationChannel restorationEnabled:YES];
NSData* data = [@"testrestortiondata" dataUsingEncoding:NSUTF8StringEncoding];
[restorationPlugin setRestorationData:data];
__block id capturedResult;
FlutterMethodCall* methodCall = [FlutterMethodCall methodCallWithMethodName:@"get" arguments:nil];
[restorationPlugin handleMethodCall:methodCall
result:^(id _Nullable result) {
capturedResult = result;
}];
XCTAssertEqual([capturedResult count], 2u);
XCTAssertEqual([capturedResult objectForKey:@"enabled"], @YES);
XCTAssertEqual([[capturedResult objectForKey:@"data"] data], data);
}
- (void)testRespondsWithNoDataWhenRestorationIsCompletedWithoutData {
FlutterRestorationPlugin* restorationPlugin =
[[FlutterRestorationPlugin alloc] initWithChannel:restorationChannel restorationEnabled:YES];
FlutterMethodCall* methodCall = [FlutterMethodCall methodCallWithMethodName:@"get" arguments:nil];
__block id capturedResult;
[restorationPlugin handleMethodCall:methodCall
result:^(id _Nullable result) {
capturedResult = result;
}];
XCTAssertNil(capturedResult);
[restorationPlugin markRestorationComplete];
XCTAssertEqual([capturedResult count], 1u);
XCTAssertEqual([capturedResult objectForKey:@"enabled"], @YES);
}
- (void)testRespondsRightAwayWithNoDataWhenRestorationIsCompleted {
FlutterRestorationPlugin* restorationPlugin =
[[FlutterRestorationPlugin alloc] initWithChannel:restorationChannel restorationEnabled:YES];
[restorationPlugin markRestorationComplete];
__block id capturedResult;
FlutterMethodCall* methodCall = [FlutterMethodCall methodCallWithMethodName:@"get" arguments:nil];
[restorationPlugin handleMethodCall:methodCall
result:^(id _Nullable result) {
capturedResult = result;
}];
XCTAssertEqual([capturedResult count], 1u);
XCTAssertEqual([capturedResult objectForKey:@"enabled"], @YES);
}
- (void)testReturnsDataSetByFramework {
FlutterRestorationPlugin* restorationPlugin =
[[FlutterRestorationPlugin alloc] initWithChannel:restorationChannel restorationEnabled:YES];
[restorationPlugin markRestorationComplete];
NSData* data = [@"testrestortiondata" dataUsingEncoding:NSUTF8StringEncoding];
FlutterMethodCall* methodCall = [FlutterMethodCall
methodCallWithMethodName:@"put"
arguments:[FlutterStandardTypedData typedDataWithBytes:data]];
[restorationPlugin handleMethodCall:methodCall
result:^(id _Nullable result) {
XCTAssertNil(result);
}];
XCTAssertEqual([restorationPlugin restorationData], data);
}
- (void)testRespondsWithDataSetByFramework {
FlutterRestorationPlugin* restorationPlugin =
[[FlutterRestorationPlugin alloc] initWithChannel:restorationChannel restorationEnabled:YES];
[restorationPlugin markRestorationComplete];
NSData* data = [@"testrestortiondata" dataUsingEncoding:NSUTF8StringEncoding];
FlutterMethodCall* methodCall = [FlutterMethodCall
methodCallWithMethodName:@"put"
arguments:[FlutterStandardTypedData typedDataWithBytes:data]];
[restorationPlugin handleMethodCall:methodCall
result:^(id _Nullable result) {
XCTAssertNil(result);
}];
XCTAssertEqual([restorationPlugin restorationData], data);
__block id capturedResult;
methodCall = [FlutterMethodCall methodCallWithMethodName:@"get" arguments:nil];
[restorationPlugin handleMethodCall:methodCall
result:^(id _Nullable result) {
capturedResult = result;
}];
XCTAssertEqual([capturedResult count], 2u);
XCTAssertEqual([capturedResult objectForKey:@"enabled"], @YES);
XCTAssertEqual([[capturedResult objectForKey:@"data"] data], data);
}
- (void)testResetClearsData {
FlutterRestorationPlugin* restorationPlugin =
[[FlutterRestorationPlugin alloc] initWithChannel:restorationChannel restorationEnabled:YES];
[restorationPlugin markRestorationComplete];
NSData* data = [@"testrestortiondata" dataUsingEncoding:NSUTF8StringEncoding];
FlutterMethodCall* methodCall = [FlutterMethodCall
methodCallWithMethodName:@"put"
arguments:[FlutterStandardTypedData typedDataWithBytes:data]];
[restorationPlugin handleMethodCall:methodCall
result:^(id _Nullable result) {
XCTAssertNil(result);
}];
XCTAssertEqual([restorationPlugin restorationData], data);
[restorationPlugin reset];
XCTAssertNil([restorationPlugin restorationData]);
__block id capturedResult;
methodCall = [FlutterMethodCall methodCallWithMethodName:@"get" arguments:nil];
[restorationPlugin handleMethodCall:methodCall
result:^(id _Nullable result) {
capturedResult = result;
}];
XCTAssertEqual([capturedResult count], 1u);
XCTAssertEqual([capturedResult objectForKey:@"enabled"], @YES);
}
@end

View File

@ -191,7 +191,8 @@ typedef enum UIAccessibilityContrast : NSInteger {
auto engine = fml::scoped_nsobject<FlutterEngine>{[[FlutterEngine alloc]
initWithName:@"io.flutter"
project:project
allowHeadlessExecution:self.engineAllowHeadlessExecution]};
allowHeadlessExecution:self.engineAllowHeadlessExecution
restorationEnabled:[self restorationIdentifier] != nil]};
if (!engine) {
return;
@ -654,6 +655,7 @@ static void sendFakeTouchEvent(FlutterEngine* engine,
[self surfaceUpdated:YES];
}
[[_engine.get() lifecycleChannel] sendMessage:@"AppLifecycleState.inactive"];
[[_engine.get() restorationPlugin] markRestorationComplete];
[super viewWillAppear:animated];
}
@ -1523,4 +1525,20 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch)
[_engine.get() dispatchPointerDataPacket:std::move(packet)];
}
#pragma mark - State Restoration
- (void)encodeRestorableStateWithCoder:(NSCoder*)coder {
NSData* restorationData = [[_engine.get() restorationPlugin] restorationData];
[coder encodeDataObject:restorationData];
}
- (void)decodeRestorableStateWithCoder:(NSCoder*)coder {
NSData* restorationData = [coder decodeDataObject];
[[_engine.get() restorationPlugin] setRestorationData:restorationData];
}
- (FlutterRestorationPlugin*)restorationPlugin {
return [_engine.get() restorationPlugin];
}
@end

View File

@ -8,6 +8,7 @@
#include "flutter/fml/memory/weak_ptr.h"
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterRestorationPlugin.h"
namespace flutter {
class FlutterPlatformViewsController;
@ -27,6 +28,7 @@ extern NSNotificationName const FlutterViewControllerShowHomeIndicator;
@property(nonatomic, readonly) BOOL isPresentingViewController;
- (fml::WeakPtr<FlutterViewController>)getWeakPtr;
- (std::shared_ptr<flutter::FlutterPlatformViewsController>&)platformViewsController;
- (FlutterRestorationPlugin*)restorationPlugin;
@end

View File

@ -207,6 +207,7 @@ void PlatformViewIOS::OnPreEngineRestart() const {
return;
}
[owner_controller_.get() platformViewsController]->Reset();
[[owner_controller_.get() restorationPlugin] reset];
}
std::unique_ptr<std::vector<std::string>> PlatformViewIOS::ComputePlatformResolvedLocales(