Skip to content

Commit d2c6732

Browse files
goderbauergspencergoog
authored andcommitted
State Restoration for iOS (flutter#23495)
1 parent 9ae5878 commit d2c6732

14 files changed

+492
-9
lines changed

ci/licenses_golden/licenses_flutter

+3
Original file line numberDiff line numberDiff line change
@@ -990,6 +990,9 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatfor
990990
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate.mm
991991
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegateTest.m
992992
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate_internal.h
993+
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterRestorationPlugin.h
994+
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterRestorationPlugin.mm
995+
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterRestorationPluginTest.mm
993996
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h
994997
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h
995998
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm

shell/platform/darwin/ios/BUILD.gn

+3
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ source_set("flutter_framework_source") {
6666
"framework/Source/FlutterPlatformViews_Internal.h",
6767
"framework/Source/FlutterPlatformViews_Internal.mm",
6868
"framework/Source/FlutterPluginAppLifeCycleDelegate.mm",
69+
"framework/Source/FlutterRestorationPlugin.h",
70+
"framework/Source/FlutterRestorationPlugin.mm",
6971
"framework/Source/FlutterTextInputDelegate.h",
7072
"framework/Source/FlutterTextInputPlugin.h",
7173
"framework/Source/FlutterTextInputPlugin.mm",
@@ -221,6 +223,7 @@ shared_library("ios_test_flutter") {
221223
"framework/Source/FlutterEngineGroupTest.mm",
222224
"framework/Source/FlutterEngineTest.mm",
223225
"framework/Source/FlutterPluginAppLifeCycleDelegateTest.m",
226+
"framework/Source/FlutterRestorationPluginTest.mm",
224227
"framework/Source/FlutterTextInputPluginTest.m",
225228
"framework/Source/FlutterViewControllerTest.mm",
226229
"framework/Source/SemanticsObjectTest.mm",

shell/platform/darwin/ios/framework/Headers/FlutterEngine.h

+35-1
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,31 @@ FLUTTER_EXPORT
135135
*/
136136
- (instancetype)initWithName:(NSString*)labelPrefix
137137
project:(nullable FlutterDartProject*)project
138-
allowHeadlessExecution:(BOOL)allowHeadlessExecution NS_DESIGNATED_INITIALIZER;
138+
allowHeadlessExecution:(BOOL)allowHeadlessExecution;
139+
140+
/**
141+
* Initialize this FlutterEngine with a `FlutterDartProject`.
142+
*
143+
* If the FlutterDartProject is not specified, the FlutterEngine will attempt to locate
144+
* the project in a default location (the flutter_assets folder in the iOS application
145+
* bundle).
146+
*
147+
* A newly initialized engine will not run the `FlutterDartProject` until either
148+
* `-runWithEntrypoint:` or `-runWithEntrypoint:libraryURI:` is called.
149+
*
150+
* @param labelPrefix The label prefix used to identify threads for this instance. Should
151+
* be unique across FlutterEngine instances, and is used in instrumentation to label
152+
* the threads used by this FlutterEngine.
153+
* @param project The `FlutterDartProject` to run.
154+
* @param allowHeadlessExecution Whether or not to allow this instance to continue
155+
* running after passing a nil `FlutterViewController` to `-setViewController:`.
156+
* @param restorationEnabled Whether state restoration is enabled. When true, the framework will
157+
* wait for the attached view controller to provide restoration data.
158+
*/
159+
- (instancetype)initWithName:(NSString*)labelPrefix
160+
project:(nullable FlutterDartProject*)project
161+
allowHeadlessExecution:(BOOL)allowHeadlessExecution
162+
restorationEnabled:(BOOL)restorationEnabled NS_DESIGNATED_INITIALIZER;
139163

140164
+ (instancetype)new NS_UNAVAILABLE;
141165

@@ -273,6 +297,16 @@ FLUTTER_EXPORT
273297
*/
274298
@property(nonatomic, readonly) FlutterMethodChannel* navigationChannel;
275299

300+
/**
301+
* The `FlutterMethodChannel` used for restoration related platform messages.
302+
*
303+
* Can be nil after `destroyContext` is called.
304+
*
305+
* @see [Restoration
306+
* Channel](https://api.flutter.dev/flutter/services/SystemChannels/restoration-constant.html)
307+
*/
308+
@property(nonatomic, readonly) FlutterMethodChannel* restorationChannel;
309+
276310
/**
277311
* The `FlutterMethodChannel` used for core platform messages, such as
278312
* information about the screen orientation.

shell/platform/darwin/ios/framework/Headers/FlutterHeadlessDartRunner.h

+23-3
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ FLUTTER_DEPRECATED("FlutterEngine should be used rather than FlutterHeadlessDart
3434
@interface FlutterHeadlessDartRunner : FlutterEngine
3535

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

5151
/**
52-
* Iniitalize this FlutterHeadlessDartRunner with a `FlutterDartProject`.
52+
* Initialize this FlutterHeadlessDartRunner with a `FlutterDartProject`.
5353
*
5454
* If the FlutterDartProject is not specified, the FlutterHeadlessDartRunner will attempt to locate
5555
* the project in a default location.
@@ -64,7 +64,27 @@ FLUTTER_DEPRECATED("FlutterEngine should be used rather than FlutterHeadlessDart
6464
*/
6565
- (instancetype)initWithName:(NSString*)labelPrefix
6666
project:(FlutterDartProject*)projectOrNil
67-
allowHeadlessExecution:(BOOL)allowHeadlessExecution NS_DESIGNATED_INITIALIZER;
67+
allowHeadlessExecution:(BOOL)allowHeadlessExecution;
68+
69+
/**
70+
* Initialize this FlutterHeadlessDartRunner with a `FlutterDartProject`.
71+
*
72+
* If the FlutterDartProject is not specified, the FlutterHeadlessDartRunner will attempt to locate
73+
* the project in a default location.
74+
*
75+
* A newly initialized engine will not run the `FlutterDartProject` until either
76+
* `-runWithEntrypoint:` or `-runWithEntrypoint:libraryURI` is called.
77+
*
78+
* @param labelPrefix The label prefix used to identify threads for this instance. Should
79+
* be unique across FlutterEngine instances
80+
* @param projectOrNil The `FlutterDartProject` to run.
81+
* @param allowHeadlessExecution Must be set to `YES`.
82+
* @param restorationEnabled Must be set to `NO`.
83+
*/
84+
- (instancetype)initWithName:(NSString*)labelPrefix
85+
project:(FlutterDartProject*)projectOrNil
86+
allowHeadlessExecution:(BOOL)allowHeadlessExecution
87+
restorationEnabled:(BOOL)restorationEnabled NS_DESIGNATED_INITIALIZER;
6888

6989
/**
7090
* Not recommended for use - will initialize with a default label ("io.flutter.headless")

shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm

+37-3
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@
1111
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h"
1212
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate_internal.h"
1313

14-
static NSString* kUIBackgroundMode = @"UIBackgroundModes";
15-
static NSString* kRemoteNotificationCapabitiliy = @"remote-notification";
16-
static NSString* kBackgroundFetchCapatibility = @"fetch";
14+
static NSString* const kUIBackgroundMode = @"UIBackgroundModes";
15+
static NSString* const kRemoteNotificationCapabitiliy = @"remote-notification";
16+
static NSString* const kBackgroundFetchCapatibility = @"fetch";
17+
static NSString* const kRestorationStateAppModificationKey = @"mod-date";
1718

1819
@interface FlutterAppDelegate ()
1920
@property(nonatomic, copy) FlutterViewController* (^rootFlutterViewControllerGetter)(void);
@@ -308,4 +309,37 @@ - (void)logCapabilityConfigurationWarningIfNeeded:(SEL)selector {
308309
}
309310
}
310311

312+
#pragma mark - State Restoration
313+
314+
- (BOOL)application:(UIApplication*)application shouldSaveApplicationState:(NSCoder*)coder {
315+
[coder encodeInt64:self.lastAppModificationTime forKey:kRestorationStateAppModificationKey];
316+
return YES;
317+
}
318+
319+
- (BOOL)application:(UIApplication*)application shouldRestoreApplicationState:(NSCoder*)coder {
320+
int64_t stateDate = [coder decodeInt64ForKey:kRestorationStateAppModificationKey];
321+
return self.lastAppModificationTime == stateDate;
322+
}
323+
324+
- (BOOL)application:(UIApplication*)application shouldSaveSecureApplicationState:(NSCoder*)coder {
325+
[coder encodeInt64:self.lastAppModificationTime forKey:kRestorationStateAppModificationKey];
326+
return YES;
327+
}
328+
329+
- (BOOL)application:(UIApplication*)application
330+
shouldRestoreSecureApplicationState:(NSCoder*)coder {
331+
int64_t stateDate = [coder decodeInt64ForKey:kRestorationStateAppModificationKey];
332+
return self.lastAppModificationTime == stateDate;
333+
}
334+
335+
- (int64_t)lastAppModificationTime {
336+
NSDate* fileDate;
337+
NSError* error = nil;
338+
[[[NSBundle mainBundle] executableURL] getResourceValue:&fileDate
339+
forKey:NSURLContentModificationDateKey
340+
error:&error];
341+
NSAssert(error == nil, @"Cannot obtain modification date of main bundle: %@", error);
342+
return [fileDate timeIntervalSince1970];
343+
}
344+
311345
@end

shell/platform/darwin/ios/framework/Source/FlutterEngine.mm

+30
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,10 @@ @implementation FlutterEngine {
7171
// Channels
7272
fml::scoped_nsobject<FlutterPlatformPlugin> _platformPlugin;
7373
fml::scoped_nsobject<FlutterTextInputPlugin> _textInputPlugin;
74+
fml::scoped_nsobject<FlutterRestorationPlugin> _restorationPlugin;
7475
fml::scoped_nsobject<FlutterMethodChannel> _localizationChannel;
7576
fml::scoped_nsobject<FlutterMethodChannel> _navigationChannel;
77+
fml::scoped_nsobject<FlutterMethodChannel> _restorationChannel;
7678
fml::scoped_nsobject<FlutterMethodChannel> _platformChannel;
7779
fml::scoped_nsobject<FlutterMethodChannel> _platformViewsChannel;
7880
fml::scoped_nsobject<FlutterMethodChannel> _textInputChannel;
@@ -84,6 +86,7 @@ @implementation FlutterEngine {
8486
int64_t _nextTextureId;
8587

8688
BOOL _allowHeadlessExecution;
89+
BOOL _restorationEnabled;
8790
FlutterBinaryMessengerRelay* _binaryMessenger;
8891
std::unique_ptr<flutter::ConnectionCollection> _connections;
8992
}
@@ -103,10 +106,21 @@ - (instancetype)initWithName:(NSString*)labelPrefix project:(FlutterDartProject*
103106
- (instancetype)initWithName:(NSString*)labelPrefix
104107
project:(FlutterDartProject*)project
105108
allowHeadlessExecution:(BOOL)allowHeadlessExecution {
109+
return [self initWithName:labelPrefix
110+
project:project
111+
allowHeadlessExecution:allowHeadlessExecution
112+
restorationEnabled:NO];
113+
}
114+
115+
- (instancetype)initWithName:(NSString*)labelPrefix
116+
project:(FlutterDartProject*)project
117+
allowHeadlessExecution:(BOOL)allowHeadlessExecution
118+
restorationEnabled:(BOOL)restorationEnabled {
106119
self = [super init];
107120
NSAssert(self, @"Super init cannot be nil");
108121
NSAssert(labelPrefix, @"labelPrefix is required");
109122

123+
_restorationEnabled = restorationEnabled;
110124
_allowHeadlessExecution = allowHeadlessExecution;
111125
_labelPrefix = [labelPrefix copy];
112126

@@ -331,12 +345,18 @@ - (FlutterPlatformPlugin*)platformPlugin {
331345
- (FlutterTextInputPlugin*)textInputPlugin {
332346
return _textInputPlugin.get();
333347
}
348+
- (FlutterRestorationPlugin*)restorationPlugin {
349+
return _restorationPlugin.get();
350+
}
334351
- (FlutterMethodChannel*)localizationChannel {
335352
return _localizationChannel.get();
336353
}
337354
- (FlutterMethodChannel*)navigationChannel {
338355
return _navigationChannel.get();
339356
}
357+
- (FlutterMethodChannel*)restorationChannel {
358+
return _restorationChannel.get();
359+
}
340360
- (FlutterMethodChannel*)platformChannel {
341361
return _platformChannel.get();
342362
}
@@ -363,6 +383,7 @@ - (NSURL*)observatoryUrl {
363383
- (void)resetChannels {
364384
_localizationChannel.reset();
365385
_navigationChannel.reset();
386+
_restorationChannel.reset();
366387
_platformChannel.reset();
367388
_platformViewsChannel.reset();
368389
_textInputChannel.reset();
@@ -413,6 +434,11 @@ - (void)setupChannels {
413434
_initialRoute = nil;
414435
}
415436

437+
_restorationChannel.reset([[FlutterMethodChannel alloc]
438+
initWithName:@"flutter/restoration"
439+
binaryMessenger:self.binaryMessenger
440+
codec:[FlutterStandardMethodCodec sharedInstance]]);
441+
416442
_platformChannel.reset([[FlutterMethodChannel alloc]
417443
initWithName:@"flutter/platform"
418444
binaryMessenger:self.binaryMessenger
@@ -452,6 +478,10 @@ - (void)setupChannels {
452478
_textInputPlugin.get().textInputDelegate = self;
453479

454480
_platformPlugin.reset([[FlutterPlatformPlugin alloc] initWithEngine:[self getWeakPtr]]);
481+
482+
_restorationPlugin.reset([[FlutterRestorationPlugin alloc]
483+
initWithChannel:_restorationChannel.get()
484+
restorationEnabled:_restorationEnabled]);
455485
}
456486

457487
- (void)maybeSetupPlatformViewChannels {

shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject_Internal.h"
1919
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h"
2020
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h"
21+
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterRestorationPlugin.h"
2122
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h"
2223
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h"
2324
#import "flutter/shell/platform/darwin/ios/platform_view_ios.h"
@@ -42,6 +43,7 @@ extern NSString* const FlutterEngineWillDealloc;
4243
- (FlutterPlatformPlugin*)platformPlugin;
4344
- (std::shared_ptr<flutter::FlutterPlatformViewsController>&)platformViewsController;
4445
- (FlutterTextInputPlugin*)textInputPlugin;
46+
- (FlutterRestorationPlugin*)restorationPlugin;
4547
- (void)launchEngine:(NSString*)entrypoint libraryURI:(NSString*)libraryOrNil;
4648
- (BOOL)createShell:(NSString*)entrypoint
4749
libraryURI:(NSString*)libraryOrNil

shell/platform/darwin/ios/framework/Source/FlutterHeadlessDartRunner.mm

+15-1
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,24 @@ - (instancetype)initWithName:(NSString*)labelPrefix
3535
allowHeadlessExecution:(BOOL)allowHeadlessExecution {
3636
NSAssert(allowHeadlessExecution == YES,
3737
@"Cannot initialize a FlutterHeadlessDartRunner without headless execution.");
38+
return [self initWithName:labelPrefix
39+
project:projectOrNil
40+
allowHeadlessExecution:allowHeadlessExecution
41+
restorationEnabled:NO];
42+
}
43+
44+
- (instancetype)initWithName:(NSString*)labelPrefix
45+
project:(FlutterDartProject*)projectOrNil
46+
allowHeadlessExecution:(BOOL)allowHeadlessExecution
47+
restorationEnabled:(BOOL)restorationEnabled {
48+
NSAssert(allowHeadlessExecution == YES,
49+
@"Cannot initialize a FlutterHeadlessDartRunner without headless execution.");
3850
return [super initWithName:labelPrefix
3951
project:projectOrNil
40-
allowHeadlessExecution:allowHeadlessExecution];
52+
allowHeadlessExecution:allowHeadlessExecution
53+
restorationEnabled:restorationEnabled];
4154
}
55+
4256
- (instancetype)init {
4357
return [self initWithName:@"io.flutter.headless" project:nil];
4458
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#ifndef SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERRESTORATIONPLUGIN_H_
6+
#define SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERRESTORATIONPLUGIN_H_
7+
8+
#import <UIKit/UIKit.h>
9+
10+
#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h"
11+
12+
@interface FlutterRestorationPlugin : NSObject
13+
- (instancetype)init NS_UNAVAILABLE;
14+
+ (instancetype)new NS_UNAVAILABLE;
15+
- (instancetype)initWithChannel:(FlutterMethodChannel*)channel
16+
restorationEnabled:(BOOL)waitForData NS_DESIGNATED_INITIALIZER;
17+
18+
@property(nonatomic, strong) NSData* restorationData;
19+
- (void)markRestorationComplete;
20+
- (void)reset;
21+
@end
22+
#endif // SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERRESTORATIONPLUGIN_H_

0 commit comments

Comments
 (0)