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