| 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/Source/FlutterCallbackCache_Internal.h"
11 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterSharedApplication.h"
12
13 FLUTTER_ASSERT_ARC
14
15 static const char* kCallbackCacheSubDir = "Library/Caches/";
16
17 static const SEL kSelectorsHandledByPlugins[] = {
18 @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:),
19 @selector(application:performFetchWithCompletionHandler:)};
20
21 @interface FlutterPluginAppLifeCycleDelegate ()
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()]];
49 if (FlutterSharedApplication.isAvailable) {
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
244 didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
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]) {
256 [delegate application:application didFailToRegisterForRemoteNotificationsWithError:error];
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
FlutterCallbackCache_Internal.h
FlutterPluginAppLifeCycleDelegate.h
kSelectorsHandledByPlugins
static const SEL kSelectorsHandledByPlugins[]
Definition: FlutterPluginAppLifeCycleDelegate.mm:17
_delegates
NSPointerArray * _delegates
Definition: FlutterPluginAppLifeCycleDelegate.mm:34
kCallbackCacheSubDir
static FLUTTER_ASSERT_ARC const char * kCallbackCacheSubDir
Definition: FlutterPluginAppLifeCycleDelegate.mm:15
FlutterSharedApplication.h
FlutterCallbackCache
Definition: FlutterCallbackCache.h:38
+[FlutterCallbackCache setCachePath:]
void setCachePath:(NSString *path)
FlutterPluginAppLifeCycleDelegate
Definition: FlutterPluginAppLifeCycleDelegate.h:16
FlutterSharedApplication
Definition: FlutterSharedApplication.h:11
FlutterSharedApplication::isAvailable
BOOL isAvailable
Definition: FlutterSharedApplication.h:20
FLUTTER_ASSERT_ARC
Definition: FlutterChannelKeyResponder.mm:13
FlutterApplicationLifeCycleDelegate-p
Definition: FlutterPlugin.h:25
-[FlutterApplicationLifeCycleDelegate-p application:didFailToRegisterForRemoteNotificationsWithError:]
void application:didFailToRegisterForRemoteNotificationsWithError:(UIApplication *application,[didFailToRegisterForRemoteNotificationsWithError] NSError *error)
-[FlutterApplicationLifeCycleDelegate-p application:didReceiveLocalNotification:]
void application:didReceiveLocalNotification:(UIApplication *application,[didReceiveLocalNotification] "See -[UIApplicationDelegate application:didReceiveLocalNotification:] deprecation", ios(4.0, 10.0) API_DEPRECATED)
-[FlutterApplicationLifeCycleDelegate-p applicationDidBecomeActive:]
void applicationDidBecomeActive:(UIApplication *application)
-[FlutterApplicationLifeCycleDelegate-p application:didRegisterForRemoteNotificationsWithDeviceToken:]
void application:didRegisterForRemoteNotificationsWithDeviceToken:(UIApplication *application,[didRegisterForRemoteNotificationsWithDeviceToken] NSData *deviceToken)
-[FlutterApplicationLifeCycleDelegate-p applicationWillTerminate:]
void applicationWillTerminate:(UIApplication *application)
-[FlutterApplicationLifeCycleDelegate-p application:didRegisterUserNotificationSettings:]
void application:didRegisterUserNotificationSettings:(UIApplication *application,[didRegisterUserNotificationSettings] "See -[UIApplicationDelegate application:didRegisterUserNotificationSettings:] deprecation", ios(8.0, 10.0) API_DEPRECATED)
-[FlutterApplicationLifeCycleDelegate-p applicationDidEnterBackground:]
void applicationDidEnterBackground:(UIApplication *application)
-[FlutterApplicationLifeCycleDelegate-p applicationWillEnterForeground:]
void applicationWillEnterForeground:(UIApplication *application)
-[FlutterApplicationLifeCycleDelegate-p applicationWillResignActive:]
void applicationWillResignActive:(UIApplication *application)