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