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:(void (^)(NSArray<id<UIUserActivityRestoring>>* __nullable
228  restorableObjects))restorationHandler {
229  if ([self.lifeCycleDelegate application:application
230  continueUserActivity:userActivity
231  restorationHandler:restorationHandler]) {
232  return YES;
233  }
234 
235  return [self handleOpenURL:userActivity.webpageURL options:@{} relayToSystemIfUnhandled:YES];
236 }
237 
238 #pragma mark - FlutterPluginRegistry methods. All delegating to the rootViewController
239 
241  if (_weakRegistrant) {
242  return _weakRegistrant;
243  }
244  if (_strongRegistrant) {
245  return _strongRegistrant;
246  }
247  return nil;
248 }
249 
250 - (void)setPluginRegistrant:(NSObject<FlutterPluginRegistrant>*)pluginRegistrant {
251  if (pluginRegistrant == (id)self) {
252  _weakRegistrant = pluginRegistrant;
253  _strongRegistrant = nil;
254  } else {
255  _weakRegistrant = nil;
256  _strongRegistrant = pluginRegistrant;
257  }
258 }
259 
260 - (NSObject<FlutterPluginRegistrar>*)registrarForPlugin:(NSString*)pluginKey {
261  FlutterViewController* flutterRootViewController = [self rootFlutterViewController];
262  if (flutterRootViewController) {
263  return [[flutterRootViewController pluginRegistry] registrarForPlugin:pluginKey];
264  }
265  return [self.launchEngine.engine registrarForPlugin:pluginKey];
266 }
267 
268 - (BOOL)hasPlugin:(NSString*)pluginKey {
269  FlutterViewController* flutterRootViewController = [self rootFlutterViewController];
270  if (flutterRootViewController) {
271  return [[flutterRootViewController pluginRegistry] hasPlugin:pluginKey];
272  }
273  return [self.launchEngine.engine hasPlugin:pluginKey];
274 }
275 
276 - (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey {
277  FlutterViewController* flutterRootViewController = [self rootFlutterViewController];
278  if (flutterRootViewController) {
279  return [[flutterRootViewController pluginRegistry] valuePublishedByPlugin:pluginKey];
280  }
281  return [self.launchEngine.engine valuePublishedByPlugin:pluginKey];
282 }
283 
284 #pragma mark - Selectors handling
285 
286 - (void)addApplicationLifeCycleDelegate:(NSObject<FlutterApplicationLifeCycleDelegate>*)delegate {
287  [self.lifeCycleDelegate addDelegate:delegate];
288 }
289 
290 #pragma mark - UIApplicationDelegate method dynamic implementation
291 
292 - (BOOL)respondsToSelector:(SEL)selector {
293  if ([self.lifeCycleDelegate isSelectorAddedDynamically:selector]) {
294  return [self delegateRespondsSelectorToPlugins:selector];
295  }
296  return [super respondsToSelector:selector];
297 }
298 
299 - (BOOL)delegateRespondsSelectorToPlugins:(SEL)selector {
300  if ([self.lifeCycleDelegate hasPluginThatRespondsToSelector:selector]) {
301  return [self.lifeCycleDelegate respondsToSelector:selector];
302  } else {
303  return NO;
304  }
305 }
306 
307 - (id)forwardingTargetForSelector:(SEL)aSelector {
308  if ([self.lifeCycleDelegate isSelectorAddedDynamically:aSelector]) {
309  [self logCapabilityConfigurationWarningIfNeeded:aSelector];
310  return self.lifeCycleDelegate;
311  }
312  return [super forwardingTargetForSelector:aSelector];
313 }
314 
315 // Mimic the logging from Apple when the capability is not set for the selectors.
316 // However the difference is that Apple logs these message when the app launches, we only
317 // log it when the method is invoked. We can possibly also log it when the app launches, but
318 // it will cause an additional scan over all the plugins.
319 - (void)logCapabilityConfigurationWarningIfNeeded:(SEL)selector {
320  NSArray* backgroundModesArray =
321  [[NSBundle mainBundle] objectForInfoDictionaryKey:kUIBackgroundMode];
322  NSSet* backgroundModesSet = [[NSSet alloc] initWithArray:backgroundModesArray];
323  if (selector == @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:)) {
324  if (![backgroundModesSet containsObject:kRemoteNotificationCapabitiliy]) {
325  NSLog(
326  @"You've implemented -[<UIApplicationDelegate> "
327  @"application:didReceiveRemoteNotification:fetchCompletionHandler:], but you still need "
328  @"to add \"remote-notification\" to the list of your supported UIBackgroundModes in your "
329  @"Info.plist.");
330  }
331  } else if (selector == @selector(application:performFetchWithCompletionHandler:)) {
332  if (![backgroundModesSet containsObject:kBackgroundFetchCapatibility]) {
333  NSLog(@"You've implemented -[<UIApplicationDelegate> "
334  @"application:performFetchWithCompletionHandler:], but you still need to add \"fetch\" "
335  @"to the list of your supported UIBackgroundModes in your Info.plist.");
336  }
337  }
338 }
339 
340 #pragma mark - State Restoration
341 
342 - (BOOL)application:(UIApplication*)application shouldSaveApplicationState:(NSCoder*)coder {
343  [coder encodeInt64:self.lastAppModificationTime forKey:kRestorationStateAppModificationKey];
344  return YES;
345 }
346 
347 - (BOOL)application:(UIApplication*)application shouldRestoreApplicationState:(NSCoder*)coder {
348  int64_t stateDate = [coder decodeInt64ForKey:kRestorationStateAppModificationKey];
349  return self.lastAppModificationTime == stateDate;
350 }
351 
352 - (BOOL)application:(UIApplication*)application shouldSaveSecureApplicationState:(NSCoder*)coder {
353  [coder encodeInt64:self.lastAppModificationTime forKey:kRestorationStateAppModificationKey];
354  return YES;
355 }
356 
357 - (BOOL)application:(UIApplication*)application
358  shouldRestoreSecureApplicationState:(NSCoder*)coder {
359  int64_t stateDate = [coder decodeInt64ForKey:kRestorationStateAppModificationKey];
360  return self.lastAppModificationTime == stateDate;
361 }
362 
363 - (int64_t)lastAppModificationTime {
364  NSDate* fileDate;
365  NSError* error = nil;
366  [[[NSBundle mainBundle] executableURL] getResourceValue:&fileDate
367  forKey:NSURLContentModificationDateKey
368  error:&error];
369  NSAssert(error == nil, @"Cannot obtain modification date of main bundle: %@", error);
370  return [fileDate timeIntervalSince1970];
371 }
372 
373 @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()