mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
State Restoration for iOS (flutter/engine#23495)
This commit is contained in:
parent
8f6cd9f6e8
commit
ea1b8adee8
@ -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
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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];
|
||||
}
|
||||
|
||||
@ -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_
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user