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