| Flutter iOS Embedder |

FlutterAppDelegate.mm
Go to the documentation of this file.
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 #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterAppDelegate.h"
6
7 #import "flutter/fml/logging.h"
8 #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPluginAppLifeCycleDelegate.h"
9 #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h"
10 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate_Test.h"
11 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h"
12 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate_internal.h"
13 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h"
14
15 FLUTTER_ASSERT_ARC
16
17 static NSString* const kUIBackgroundMode = @"UIBackgroundModes";
18 static NSString* const kRemoteNotificationCapabitiliy = @"remote-notification";
19 static NSString* const kBackgroundFetchCapatibility = @"fetch";
20 static NSString* const kRestorationStateAppModificationKey = @"mod-date";
21
22 @interface FlutterAppDelegate ()
23 @property(nonatomic, copy) FlutterViewController* (^rootFlutterViewControllerGetter)(void);
24 @property(nonatomic, strong) FlutterPluginAppLifeCycleDelegate* lifeCycleDelegate;
25 @end
26
27 @implementation FlutterAppDelegate
28
29 - (instancetype)init {
30 if (self = [super init]) {
31 _lifeCycleDelegate = [[FlutterPluginAppLifeCycleDelegate alloc] init];
32 }
33 return self;
34 }
35
36 - (BOOL)application:(UIApplication*)application
37 willFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
38 return [self.lifeCycleDelegate application:application
39 willFinishLaunchingWithOptions:launchOptions];
40 }
41
42 - (BOOL)application:(UIApplication*)application
43 didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
44 return [self.lifeCycleDelegate application:application
45 didFinishLaunchingWithOptions:launchOptions];
46 }
47
48 // Returns the key window's rootViewController, if it's a FlutterViewController.
49 // Otherwise, returns nil.
50 - (FlutterViewController*)rootFlutterViewController {
51 if (_rootFlutterViewControllerGetter != nil) {
52 return _rootFlutterViewControllerGetter();
53 }
54 UIViewController* rootViewController = _window.rootViewController;
55 if ([rootViewController isKindOfClass:[FlutterViewController class]]) {
56 return (FlutterViewController*)rootViewController;
57 }
58 return nil;
59 }
60
61 // Do not remove, some clients may be calling these via `super`.
62 - (void)applicationDidEnterBackground:(UIApplication*)application {
63 }
64
65 // Do not remove, some clients may be calling these via `super`.
66 - (void)applicationWillEnterForeground:(UIApplication*)application {
67 }
68
69 // Do not remove, some clients may be calling these via `super`.
70 - (void)applicationWillResignActive:(UIApplication*)application {
71 }
72
73 // Do not remove, some clients may be calling these via `super`.
74 - (void)applicationDidBecomeActive:(UIApplication*)application {
75 }
76
77 // Do not remove, some clients may be calling these via `super`.
78 - (void)applicationWillTerminate:(UIApplication*)application {
79 }
80
81 #pragma GCC diagnostic push
82 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
83 - (void)application:(UIApplication*)application
84 didRegisterUserNotificationSettings:(UIUserNotificationSettings*)notificationSettings {
85 [self.lifeCycleDelegate application:application
86 didRegisterUserNotificationSettings:notificationSettings];
87 }
88 #pragma GCC diagnostic pop
89
90 - (void)application:(UIApplication*)application
91 didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken {
92 [self.lifeCycleDelegate application:application
93 didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
94 }
95
96 - (void)application:(UIApplication*)application
97 didFailToRegisterForRemoteNotificationsWithError:(NSError*)error {
98 [self.lifeCycleDelegate application:application
99 didFailToRegisterForRemoteNotificationsWithError:error];
100 }
101
102 #pragma GCC diagnostic push
103 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
104 - (void)application:(UIApplication*)application
105 didReceiveLocalNotification:(UILocalNotification*)notification {
106 [self.lifeCycleDelegate application:application didReceiveLocalNotification:notification];
107 }
108 #pragma GCC diagnostic pop
109
110 - (void)userNotificationCenter:(UNUserNotificationCenter*)center
111 willPresentNotification:(UNNotification*)notification
112 withCompletionHandler:
113 (void (^)(UNNotificationPresentationOptions options))completionHandler {
114 if ([self.lifeCycleDelegate respondsToSelector:_cmd]) {
115 [self.lifeCycleDelegate userNotificationCenter:center
116 willPresentNotification:notification
117 withCompletionHandler:completionHandler];
118 }
119 }
120
121 /**
122 * Calls all plugins registered for `UNUserNotificationCenterDelegate` callbacks.
123 */
124 - (void)userNotificationCenter:(UNUserNotificationCenter*)center
125 didReceiveNotificationResponse:(UNNotificationResponse*)response
126 withCompletionHandler:(void (^)(void))completionHandler {
127 if ([self.lifeCycleDelegate respondsToSelector:_cmd]) {
128 [self.lifeCycleDelegate userNotificationCenter:center
129 didReceiveNotificationResponse:response
130 withCompletionHandler:completionHandler];
131 }
132 }
133
134 - (BOOL)isFlutterDeepLinkingEnabled {
135 NSNumber* isDeepLinkingEnabled =
136 [[NSBundle mainBundle] objectForInfoDictionaryKey:@"FlutterDeepLinkingEnabled"];
137 // if not set, return YES
138 return isDeepLinkingEnabled ? [isDeepLinkingEnabled boolValue] : YES;
139 }
140
141 // This method is called when opening an URL with custom schemes.
142 - (BOOL)application:(UIApplication*)application
143 openURL:(NSURL*)url
144 options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options {
145 if ([self.lifeCycleDelegate application:application openURL:url options:options]) {
146 return YES;
147 }
148
149 // Relaying to the system here will case an infinite loop, so we don't do it here.
150 return [self handleOpenURL:url options:options relayToSystemIfUnhandled:NO];
151 }
152
153 // Helper function for opening an URL, either with a custom scheme or a http/https scheme.
154 - (BOOL)handleOpenURL:(NSURL*)url
155 options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options
156 relayToSystemIfUnhandled:(BOOL)throwBack {
157 if (![self isFlutterDeepLinkingEnabled]) {
158 return NO;
159 }
160
161 FlutterViewController* flutterViewController = [self rootFlutterViewController];
162 if (flutterViewController) {
163 [flutterViewController sendDeepLinkToFramework:url
164 completionHandler:^(BOOL success) {
165 if (!success && throwBack) {
166 // throw it back to iOS
167 [UIApplication.sharedApplication openURL:url];
168 }
169 }];
170 } else {
171 FML_LOG(ERROR) << "Attempting to open an URL without a Flutter RootViewController.";
172 return NO;
173 }
174 return YES;
175 }
176
177 - (BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)url {
178 return [self.lifeCycleDelegate application:application handleOpenURL:url];
179 }
180
181 - (BOOL)application:(UIApplication*)application
182 openURL:(NSURL*)url
183 sourceApplication:(NSString*)sourceApplication
184 annotation:(id)annotation {
185 return [self.lifeCycleDelegate application:application
186 openURL:url
187 sourceApplication:sourceApplication
188 annotation:annotation];
189 }
190
191 - (void)application:(UIApplication*)application
192 performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
193 completionHandler:(void (^)(BOOL succeeded))completionHandler {
194 [self.lifeCycleDelegate application:application
195 performActionForShortcutItem:shortcutItem
196 completionHandler:completionHandler];
197 }
198
199 - (void)application:(UIApplication*)application
200 handleEventsForBackgroundURLSession:(nonnull NSString*)identifier
201 completionHandler:(nonnull void (^)())completionHandler {
202 [self.lifeCycleDelegate application:application
203 handleEventsForBackgroundURLSession:identifier
204 completionHandler:completionHandler];
205 }
206
207 // This method is called when opening an URL with a http/https scheme.
208 - (BOOL)application:(UIApplication*)application
209 continueUserActivity:(NSUserActivity*)userActivity
210 restorationHandler:
211 (void (^)(NSArray<id<UIUserActivityRestoring>>* __nullable restorableObjects))
212 restorationHandler {
213 if ([self.lifeCycleDelegate application:application
214 continueUserActivity:userActivity
215 restorationHandler:restorationHandler]) {
216 return YES;
217 }
218
219 return [self handleOpenURL:userActivity.webpageURL options:@{} relayToSystemIfUnhandled:YES];
220 }
221
222 #pragma mark - FlutterPluginRegistry methods. All delegating to the rootViewController
223
224 - (NSObject<FlutterPluginRegistrar>*)registrarForPlugin:(NSString*)pluginKey {
225 FlutterViewController* flutterRootViewController = [self rootFlutterViewController];
226 if (flutterRootViewController) {
227 return [[flutterRootViewController pluginRegistry] registrarForPlugin:pluginKey];
228 }
229 return nil;
230 }
231
232 - (BOOL)hasPlugin:(NSString*)pluginKey {
233 FlutterViewController* flutterRootViewController = [self rootFlutterViewController];
234 if (flutterRootViewController) {
235 return [[flutterRootViewController pluginRegistry] hasPlugin:pluginKey];
236 }
237 return false;
238 }
239
240 - (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey {
241 FlutterViewController* flutterRootViewController = [self rootFlutterViewController];
242 if (flutterRootViewController) {
243 return [[flutterRootViewController pluginRegistry] valuePublishedByPlugin:pluginKey];
244 }
245 return nil;
246 }
247
248 #pragma mark - Selectors handling
249
250 - (void)addApplicationLifeCycleDelegate:(NSObject<FlutterApplicationLifeCycleDelegate>*)delegate {
251 [self.lifeCycleDelegate addDelegate:delegate];
252 }
253
254 #pragma mark - UIApplicationDelegate method dynamic implementation
255
256 - (BOOL)respondsToSelector:(SEL)selector {
257 if ([self.lifeCycleDelegate isSelectorAddedDynamically:selector]) {
258 return [self delegateRespondsSelectorToPlugins:selector];
259 }
260 return [super respondsToSelector:selector];
261 }
262
263 - (BOOL)delegateRespondsSelectorToPlugins:(SEL)selector {
264 if ([self.lifeCycleDelegate hasPluginThatRespondsToSelector:selector]) {
265 return [self.lifeCycleDelegate respondsToSelector:selector];
266 } else {
267 return NO;
268 }
269 }
270
271 - (id)forwardingTargetForSelector:(SEL)aSelector {
272 if ([self.lifeCycleDelegate isSelectorAddedDynamically:aSelector]) {
273 [self logCapabilityConfigurationWarningIfNeeded:aSelector];
274 return self.lifeCycleDelegate;
275 }
276 return [super forwardingTargetForSelector:aSelector];
277 }
278
279 // Mimic the logging from Apple when the capability is not set for the selectors.
280 // However the difference is that Apple logs these message when the app launches, we only
281 // log it when the method is invoked. We can possibly also log it when the app launches, but
282 // it will cause an additional scan over all the plugins.
283 - (void)logCapabilityConfigurationWarningIfNeeded:(SEL)selector {
284 NSArray* backgroundModesArray =
285 [[NSBundle mainBundle] objectForInfoDictionaryKey:kUIBackgroundMode];
286 NSSet* backgroundModesSet = [[NSSet alloc] initWithArray:backgroundModesArray];
287 if (selector == @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:)) {
288 if (![backgroundModesSet containsObject:kRemoteNotificationCapabitiliy]) {
289 NSLog(
290 @"You've implemented -[<UIApplicationDelegate> "
291 @"application:didReceiveRemoteNotification:fetchCompletionHandler:], but you still need "
292 @"to add \"remote-notification\" to the list of your supported UIBackgroundModes in your "
293 @"Info.plist.");
294 }
295 } else if (selector == @selector(application:performFetchWithCompletionHandler:)) {
296 if (![backgroundModesSet containsObject:kBackgroundFetchCapatibility]) {
297 NSLog(@"You've implemented -[<UIApplicationDelegate> "
298 @"application:performFetchWithCompletionHandler:], but you still need to add \"fetch\" "
299 @"to the list of your supported UIBackgroundModes in your Info.plist.");
300 }
301 }
302 }
303
304 #pragma mark - State Restoration
305
306 - (BOOL)application:(UIApplication*)application shouldSaveApplicationState:(NSCoder*)coder {
307 [coder encodeInt64:self.lastAppModificationTime forKey:kRestorationStateAppModificationKey];
308 return YES;
309 }
310
311 - (BOOL)application:(UIApplication*)application shouldRestoreApplicationState:(NSCoder*)coder {
312 int64_t stateDate = [coder decodeInt64ForKey:kRestorationStateAppModificationKey];
313 return self.lastAppModificationTime == stateDate;
314 }
315
316 - (BOOL)application:(UIApplication*)application shouldSaveSecureApplicationState:(NSCoder*)coder {
317 [coder encodeInt64:self.lastAppModificationTime forKey:kRestorationStateAppModificationKey];
318 return YES;
319 }
320
321 - (BOOL)application:(UIApplication*)application
322 shouldRestoreSecureApplicationState:(NSCoder*)coder {
323 int64_t stateDate = [coder decodeInt64ForKey:kRestorationStateAppModificationKey];
324 return self.lastAppModificationTime == stateDate;
325 }
326
327 - (int64_t)lastAppModificationTime {
328 NSDate* fileDate;
329 NSError* error = nil;
330 [[[NSBundle mainBundle] executableURL] getResourceValue:&fileDate
331 forKey:NSURLContentModificationDateKey
332 error:&error];
333 NSAssert(error == nil, @"Cannot obtain modification date of main bundle: %@", error);
334 return [fileDate timeIntervalSince1970];
335 }
336
337 @end
FlutterAppDelegate.h
kRemoteNotificationCapabitiliy
static NSString *const kRemoteNotificationCapabitiliy
Definition: FlutterAppDelegate.mm:18
kUIBackgroundMode
static FLUTTER_ASSERT_ARC NSString *const kUIBackgroundMode
Definition: FlutterAppDelegate.mm:17
kBackgroundFetchCapatibility
static NSString *const kBackgroundFetchCapatibility
Definition: FlutterAppDelegate.mm:19
kRestorationStateAppModificationKey
static NSString *const kRestorationStateAppModificationKey
Definition: FlutterAppDelegate.mm:20
FlutterAppDelegate_Test.h
FlutterEngine_Internal.h
FlutterPluginAppLifeCycleDelegate.h
FlutterPluginAppLifeCycleDelegate_internal.h
FlutterViewController.h
FlutterViewController_Internal.h
FlutterAppDelegate(Test)::rootFlutterViewControllerGetter
FlutterViewController *(^ rootFlutterViewControllerGetter)(void)
FlutterAppDelegate
Definition: FlutterAppDelegate.h:28
FlutterPluginAppLifeCycleDelegate
Definition: FlutterPluginAppLifeCycleDelegate.h:16
FlutterViewController
Definition: FlutterViewController.h:57
-[FlutterViewController pluginRegistry]
id< FlutterPluginRegistry > pluginRegistry()
Definition: FlutterViewController.mm:2379
FLUTTER_ASSERT_ARC
Definition: FlutterChannelKeyResponder.mm:13
FlutterApplicationLifeCycleDelegate-p
Definition: FlutterPlugin.h:25
FlutterPluginRegistrar-p
Definition: FlutterPlugin.h:283