Flutter iOS Embedder
FlutterPluginAppLifeCycleDelegate.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 #include "flutter/fml/logging.h"
8 #include "flutter/fml/paths.h"
9 #include "flutter/lib/ui/plugins/callback_cache.h"
12 
14 
15 static const char* kCallbackCacheSubDir = "Library/Caches/";
16 
17 static const SEL kSelectorsHandledByPlugins[] = {
18  @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:),
19  @selector(application:performFetchWithCompletionHandler:)};
20 
22 - (void)handleDidEnterBackground:(NSNotification*)notification
23  NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions");
24 - (void)handleWillEnterForeground:(NSNotification*)notification
25  NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions");
26 - (void)handleWillResignActive:(NSNotification*)notification
27  NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions");
28 - (void)handleDidBecomeActive:(NSNotification*)notification
29  NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions");
30 - (void)handleWillTerminate:(NSNotification*)notification
31  NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions");
32 @end
33 
34 @implementation FlutterPluginAppLifeCycleDelegate {
35  UIBackgroundTaskIdentifier _debugBackgroundTask;
36 
37  // Weak references to registered plugins.
38  NSPointerArray* _delegates;
39 }
40 
41 - (void)addObserverFor:(NSString*)name selector:(SEL)selector {
42  [[NSNotificationCenter defaultCenter] addObserver:self selector:selector name:name object:nil];
43 }
44 
45 - (instancetype)init {
46  if (self = [super init]) {
47  std::string cachePath = fml::paths::JoinPaths({getenv("HOME"), kCallbackCacheSubDir});
48  [FlutterCallbackCache setCachePath:[NSString stringWithUTF8String:cachePath.c_str()]];
50  [self addObserverFor:UIApplicationDidEnterBackgroundNotification
51  selector:@selector(handleDidEnterBackground:)];
52  [self addObserverFor:UIApplicationWillEnterForegroundNotification
53  selector:@selector(handleWillEnterForeground:)];
54  [self addObserverFor:UIApplicationWillResignActiveNotification
55  selector:@selector(handleWillResignActive:)];
56  [self addObserverFor:UIApplicationDidBecomeActiveNotification
57  selector:@selector(handleDidBecomeActive:)];
58  [self addObserverFor:UIApplicationWillTerminateNotification
59  selector:@selector(handleWillTerminate:)];
60  }
61  _delegates = [NSPointerArray weakObjectsPointerArray];
62  _debugBackgroundTask = UIBackgroundTaskInvalid;
63  }
64  return self;
65 }
66 
67 static BOOL IsPowerOfTwo(NSUInteger x) {
68  return x != 0 && (x & (x - 1)) == 0;
69 }
70 
71 - (BOOL)isSelectorAddedDynamically:(SEL)selector {
72  for (const SEL& aSelector : kSelectorsHandledByPlugins) {
73  if (selector == aSelector) {
74  return YES;
75  }
76  }
77  return NO;
78 }
79 
80 - (BOOL)hasPluginThatRespondsToSelector:(SEL)selector {
81  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in [_delegates allObjects]) {
82  if (!delegate) {
83  continue;
84  }
85  if ([delegate respondsToSelector:selector]) {
86  return YES;
87  }
88  }
89  return NO;
90 }
91 
92 - (void)addDelegate:(NSObject<FlutterApplicationLifeCycleDelegate>*)delegate {
93  [_delegates addPointer:(__bridge void*)delegate];
94  if (IsPowerOfTwo([_delegates count])) {
95  [_delegates compact];
96  }
97 }
98 
99 - (BOOL)application:(UIApplication*)application
100  didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
101  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in [_delegates allObjects]) {
102  if (!delegate) {
103  continue;
104  }
105  if ([delegate respondsToSelector:_cmd]) {
106  if (![delegate application:application didFinishLaunchingWithOptions:launchOptions]) {
107  return NO;
108  }
109  }
110  }
111  return YES;
112 }
113 
114 - (BOOL)application:(UIApplication*)application
115  willFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
116  flutter::DartCallbackCache::LoadCacheFromDisk();
117  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in [_delegates allObjects]) {
118  if (!delegate) {
119  continue;
120  }
121  if ([delegate respondsToSelector:_cmd]) {
122  if (![delegate application:application willFinishLaunchingWithOptions:launchOptions]) {
123  return NO;
124  }
125  }
126  }
127  return YES;
128 }
129 
130 - (void)handleDidEnterBackground:(NSNotification*)notification
131  NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions") {
132  UIApplication* application = [UIApplication sharedApplication];
133 #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
134  // The following keeps the Flutter session alive when the device screen locks
135  // in debug mode. It allows continued use of features like hot reload and
136  // taking screenshots once the device unlocks again.
137  //
138  // Note the name is not an identifier and multiple instances can exist.
139  _debugBackgroundTask = [application
140  beginBackgroundTaskWithName:@"Flutter debug task"
141  expirationHandler:^{
142  if (_debugBackgroundTask != UIBackgroundTaskInvalid) {
143  [application endBackgroundTask:_debugBackgroundTask];
144  _debugBackgroundTask = UIBackgroundTaskInvalid;
145  }
146  FML_LOG(WARNING)
147  << "\nThe OS has terminated the Flutter debug connection for being "
148  "inactive in the background for too long.\n\n"
149  "There are no errors with your Flutter application.\n\n"
150  "To reconnect, launch your application again via 'flutter run'";
151  }];
152 #endif // FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
153  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
154  if (!delegate) {
155  continue;
156  }
157  if ([delegate respondsToSelector:@selector(applicationDidEnterBackground:)]) {
158  [delegate applicationDidEnterBackground:application];
159  }
160  }
161 }
162 
163 - (void)handleWillEnterForeground:(NSNotification*)notification
164  NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions") {
165  UIApplication* application = [UIApplication sharedApplication];
166 #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
167  if (_debugBackgroundTask != UIBackgroundTaskInvalid) {
168  [application endBackgroundTask:_debugBackgroundTask];
169  _debugBackgroundTask = UIBackgroundTaskInvalid;
170  }
171 #endif // FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
172  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
173  if (!delegate) {
174  continue;
175  }
176  if ([delegate respondsToSelector:@selector(applicationWillEnterForeground:)]) {
177  [delegate applicationWillEnterForeground:application];
178  }
179  }
180 }
181 
182 - (void)handleWillResignActive:(NSNotification*)notification
183  NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions") {
184  UIApplication* application = [UIApplication sharedApplication];
185  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
186  if (!delegate) {
187  continue;
188  }
189  if ([delegate respondsToSelector:@selector(applicationWillResignActive:)]) {
190  [delegate applicationWillResignActive:application];
191  }
192  }
193 }
194 
195 - (void)handleDidBecomeActive:(NSNotification*)notification
196  NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions") {
197  UIApplication* application = [UIApplication sharedApplication];
198  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
199  if (!delegate) {
200  continue;
201  }
202  if ([delegate respondsToSelector:@selector(applicationDidBecomeActive:)]) {
203  [delegate applicationDidBecomeActive:application];
204  }
205  }
206 }
207 
208 - (void)handleWillTerminate:(NSNotification*)notification
209  NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions") {
210  UIApplication* application = [UIApplication sharedApplication];
211  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
212  if (!delegate) {
213  continue;
214  }
215  if ([delegate respondsToSelector:@selector(applicationWillTerminate:)]) {
216  [delegate applicationWillTerminate:application];
217  }
218  }
219 }
220 
221 #pragma GCC diagnostic push
222 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
223 - (void)application:(UIApplication*)application
224  didRegisterUserNotificationSettings:(UIUserNotificationSettings*)notificationSettings {
225  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
226  if (!delegate) {
227  continue;
228  }
229  if ([delegate respondsToSelector:_cmd]) {
230  [delegate application:application didRegisterUserNotificationSettings:notificationSettings];
231  }
232  }
233 }
234 #pragma GCC diagnostic pop
235 
236 - (void)application:(UIApplication*)application
237  didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken {
238  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
239  if (!delegate) {
240  continue;
241  }
242  if ([delegate respondsToSelector:_cmd]) {
243  [delegate application:application
245  }
246  }
247 }
248 
249 - (void)application:(UIApplication*)application
250  didFailToRegisterForRemoteNotificationsWithError:(NSError*)error {
251  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
252  if (!delegate) {
253  continue;
254  }
255  if ([delegate respondsToSelector:_cmd]) {
257  }
258  }
259 }
260 
261 - (void)application:(UIApplication*)application
262  didReceiveRemoteNotification:(NSDictionary*)userInfo
263  fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
264  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
265  if (!delegate) {
266  continue;
267  }
268  if ([delegate respondsToSelector:_cmd]) {
269  if ([delegate application:application
270  didReceiveRemoteNotification:userInfo
271  fetchCompletionHandler:completionHandler]) {
272  return;
273  }
274  }
275  }
276 }
277 
278 #pragma GCC diagnostic push
279 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
280 - (void)application:(UIApplication*)application
281  didReceiveLocalNotification:(UILocalNotification*)notification {
282  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
283  if (!delegate) {
284  continue;
285  }
286  if ([delegate respondsToSelector:_cmd]) {
287  [delegate application:application didReceiveLocalNotification:notification];
288  }
289  }
290 }
291 #pragma GCC diagnostic pop
292 
293 - (void)userNotificationCenter:(UNUserNotificationCenter*)center
294  willPresentNotification:(UNNotification*)notification
295  withCompletionHandler:
296  (void (^)(UNNotificationPresentationOptions options))completionHandler {
297  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
298  if ([delegate respondsToSelector:_cmd]) {
299  [delegate userNotificationCenter:center
300  willPresentNotification:notification
301  withCompletionHandler:completionHandler];
302  }
303  }
304 }
305 
306 - (void)userNotificationCenter:(UNUserNotificationCenter*)center
307  didReceiveNotificationResponse:(UNNotificationResponse*)response
308  withCompletionHandler:(void (^)(void))completionHandler {
309  for (id<FlutterApplicationLifeCycleDelegate> delegate in _delegates) {
310  if ([delegate respondsToSelector:_cmd]) {
311  [delegate userNotificationCenter:center
312  didReceiveNotificationResponse:response
313  withCompletionHandler:completionHandler];
314  }
315  }
316 }
317 
318 - (BOOL)application:(UIApplication*)application
319  openURL:(NSURL*)url
320  options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options {
321  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
322  if (!delegate) {
323  continue;
324  }
325  if ([delegate respondsToSelector:_cmd]) {
326  if ([delegate application:application openURL:url options:options]) {
327  return YES;
328  }
329  }
330  }
331  return NO;
332 }
333 
334 - (BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)url {
335  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
336  if (!delegate) {
337  continue;
338  }
339  if ([delegate respondsToSelector:_cmd]) {
340  if ([delegate application:application handleOpenURL:url]) {
341  return YES;
342  }
343  }
344  }
345  return NO;
346 }
347 
348 - (BOOL)application:(UIApplication*)application
349  openURL:(NSURL*)url
350  sourceApplication:(NSString*)sourceApplication
351  annotation:(id)annotation {
352  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
353  if (!delegate) {
354  continue;
355  }
356  if ([delegate respondsToSelector:_cmd]) {
357  if ([delegate application:application
358  openURL:url
359  sourceApplication:sourceApplication
360  annotation:annotation]) {
361  return YES;
362  }
363  }
364  }
365  return NO;
366 }
367 
368 - (void)application:(UIApplication*)application
369  performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
370  completionHandler:(void (^)(BOOL succeeded))completionHandler {
371  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
372  if (!delegate) {
373  continue;
374  }
375  if ([delegate respondsToSelector:_cmd]) {
376  if ([delegate application:application
377  performActionForShortcutItem:shortcutItem
378  completionHandler:completionHandler]) {
379  return;
380  }
381  }
382  }
383 }
384 
385 - (BOOL)application:(UIApplication*)application
386  handleEventsForBackgroundURLSession:(nonnull NSString*)identifier
387  completionHandler:(nonnull void (^)())completionHandler {
388  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
389  if (!delegate) {
390  continue;
391  }
392  if ([delegate respondsToSelector:_cmd]) {
393  if ([delegate application:application
394  handleEventsForBackgroundURLSession:identifier
395  completionHandler:completionHandler]) {
396  return YES;
397  }
398  }
399  }
400  return NO;
401 }
402 
403 - (BOOL)application:(UIApplication*)application
404  performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
405  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
406  if (!delegate) {
407  continue;
408  }
409  if ([delegate respondsToSelector:_cmd]) {
410  if ([delegate application:application performFetchWithCompletionHandler:completionHandler]) {
411  return YES;
412  }
413  }
414  }
415  return NO;
416 }
417 
418 - (BOOL)application:(UIApplication*)application
419  continueUserActivity:(NSUserActivity*)userActivity
420  restorationHandler:(void (^)(NSArray*))restorationHandler {
421  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
422  if (!delegate) {
423  continue;
424  }
425  if ([delegate respondsToSelector:_cmd]) {
426  if ([delegate application:application
427  continueUserActivity:userActivity
428  restorationHandler:restorationHandler]) {
429  return YES;
430  }
431  }
432  }
433  return NO;
434 }
435 @end
static const SEL kSelectorsHandledByPlugins[]
NSPointerArray * _delegates
static FLUTTER_ASSERT_ARC const char * kCallbackCacheSubDir
void setCachePath:(NSString *path)
void application:didFailToRegisterForRemoteNotificationsWithError:(UIApplication *application,[didFailToRegisterForRemoteNotificationsWithError] NSError *error)
void application:didReceiveLocalNotification:(UIApplication *application,[didReceiveLocalNotification] "See -[UIApplicationDelegate application:didReceiveLocalNotification:] deprecation", ios(4.0, 10.0) API_DEPRECATED)
void applicationDidBecomeActive:(UIApplication *application)
void application:didRegisterForRemoteNotificationsWithDeviceToken:(UIApplication *application,[didRegisterForRemoteNotificationsWithDeviceToken] NSData *deviceToken)
void applicationWillTerminate:(UIApplication *application)
void application:didRegisterUserNotificationSettings:(UIApplication *application,[didRegisterUserNotificationSettings] "See -[UIApplicationDelegate application:didRegisterUserNotificationSettings:] deprecation", ios(8.0, 10.0) API_DEPRECATED)
void applicationDidEnterBackground:(UIApplication *application)
void applicationWillEnterForeground:(UIApplication *application)
void applicationWillResignActive:(UIApplication *application)