| 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
12 FLUTTER_ASSERT_ARC
13
14 static const char* kCallbackCacheSubDir = "Library/Caches/";
15
16 static const SEL kSelectorsHandledByPlugins[] = {
17 @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:),
18 @selector(application:performFetchWithCompletionHandler:)};
19
20 @interface FlutterPluginAppLifeCycleDelegate ()
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
33 @implementation FlutterPluginAppLifeCycleDelegate {
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