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/fml/logging.h"
15 
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
static NSString *const kRemoteNotificationCapabitiliy
static FLUTTER_ASSERT_ARC NSString *const kUIBackgroundMode
static NSString *const kBackgroundFetchCapatibility
static NSString *const kRestorationStateAppModificationKey
FlutterViewController *(^ rootFlutterViewControllerGetter)(void)
id< FlutterPluginRegistry > pluginRegistry()