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