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