| 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
5 #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPluginAppLifeCycleDelegate.h"
6
7 #include "flutter/fml/logging.h"
8 #include "flutter/fml/paths.h"
9 #include "flutter/lib/ui/plugins/callback_cache.h"
10 #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h"
11 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterCallbackCache_Internal.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
19 @interface FlutterPluginAppLifeCycleDelegate ()
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
32 @implementation FlutterPluginAppLifeCycleDelegate {
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