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