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