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