| Flutter iOS Embedder |

FlutterAppDelegate.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/FlutterAppDelegate.h"
6
7 #import "flutter/fml/logging.h"
8 #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPluginAppLifeCycleDelegate.h"
9 #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h"
10 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate_Test.h"
11 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h"
12 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate_internal.h"
13
14 FLUTTER_ASSERT_ARC
15
16 static NSString* const kUIBackgroundMode = @"UIBackgroundModes";
17 static NSString* const kRemoteNotificationCapabitiliy = @"remote-notification";
18 static NSString* const kBackgroundFetchCapatibility = @"fetch";
19 static NSString* const kRestorationStateAppModificationKey = @"mod-date";
20
21 @interface FlutterAppDelegate ()
22 @property(nonatomic, copy) FlutterViewController* (^rootFlutterViewControllerGetter)(void);
23 @property(nonatomic, strong) FlutterPluginAppLifeCycleDelegate* lifeCycleDelegate;
24 @end
25
26 @implementation FlutterAppDelegate
27
28 - (instancetype)init {
29 if (self = [super init]) {
30 _lifeCycleDelegate = [[FlutterPluginAppLifeCycleDelegate alloc] init];
31 }
32 return self;
33 }
34
35 - (BOOL)application:(UIApplication*)application
36 willFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
37 return [self.lifeCycleDelegate application:application
38 willFinishLaunchingWithOptions:launchOptions];
39 }
40
41 - (BOOL)application:(UIApplication*)application
42 didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
43 return [self.lifeCycleDelegate application:application
44 didFinishLaunchingWithOptions:launchOptions];
45 }
46
47 // Returns the key window's rootViewController, if it's a FlutterViewController.
48 // Otherwise, returns nil.
49 - (FlutterViewController*)rootFlutterViewController {
50 if (_rootFlutterViewControllerGetter != nil) {
51 return _rootFlutterViewControllerGetter();
52 }
53 UIViewController* rootViewController = _window.rootViewController;
54 if ([rootViewController isKindOfClass:[FlutterViewController class]]) {
55 return (FlutterViewController*)rootViewController;
56 }
57 return nil;
58 }
59
60 // Do not remove, some clients may be calling these via `super`.
61 - (void)applicationDidEnterBackground:(UIApplication*)application {
62 }
63
64 // Do not remove, some clients may be calling these via `super`.
65 - (void)applicationWillEnterForeground:(UIApplication*)application {
66 }
67
68 // Do not remove, some clients may be calling these via `super`.
69 - (void)applicationWillResignActive:(UIApplication*)application {
70 }
71
72 // Do not remove, some clients may be calling these via `super`.
73 - (void)applicationDidBecomeActive:(UIApplication*)application {
74 }
75
76 // Do not remove, some clients may be calling these via `super`.
77 - (void)applicationWillTerminate:(UIApplication*)application {
78 }
79
80 #pragma GCC diagnostic push
81 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
82 - (void)application:(UIApplication*)application
83 didRegisterUserNotificationSettings:(UIUserNotificationSettings*)notificationSettings {
84 [self.lifeCycleDelegate application:application
85 didRegisterUserNotificationSettings:notificationSettings];
86 }
87 #pragma GCC diagnostic pop
88
89 - (void)application:(UIApplication*)application
90 didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken {
91 [self.lifeCycleDelegate application:application
92 didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
93 }
94
95 - (void)application:(UIApplication*)application
96 didFailToRegisterForRemoteNotificationsWithError:(NSError*)error {
97 [self.lifeCycleDelegate application:application
98 didFailToRegisterForRemoteNotificationsWithError:error];
99 }
100
101 #pragma GCC diagnostic push
102 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
103 - (void)application:(UIApplication*)application
104 didReceiveLocalNotification:(UILocalNotification*)notification {
105 [self.lifeCycleDelegate application:application didReceiveLocalNotification:notification];
106 }
107 #pragma GCC diagnostic pop
108
109 - (void)userNotificationCenter:(UNUserNotificationCenter*)center
110 willPresentNotification:(UNNotification*)notification
111 withCompletionHandler:
112 (void (^)(UNNotificationPresentationOptions options))completionHandler {
113 if ([self.lifeCycleDelegate respondsToSelector:_cmd]) {
114 [self.lifeCycleDelegate userNotificationCenter:center
115 willPresentNotification:notification
116 withCompletionHandler:completionHandler];
117 }
118 }
119
120 /**
121 * Calls all plugins registered for `UNUserNotificationCenterDelegate` callbacks.
122 */
123 - (void)userNotificationCenter:(UNUserNotificationCenter*)center
124 didReceiveNotificationResponse:(UNNotificationResponse*)response
125 withCompletionHandler:(void (^)(void))completionHandler {
126 if ([self.lifeCycleDelegate respondsToSelector:_cmd]) {
127 [self.lifeCycleDelegate userNotificationCenter:center
128 didReceiveNotificationResponse:response
129 withCompletionHandler:completionHandler];
130 }
131 }
132
133 - (BOOL)isFlutterDeepLinkingEnabled {
134 NSNumber* isDeepLinkingEnabled =
135 [[NSBundle mainBundle] objectForInfoDictionaryKey:@"FlutterDeepLinkingEnabled"];
136 // if not set, return YES
137 return isDeepLinkingEnabled ? [isDeepLinkingEnabled boolValue] : YES;
138 }
139
140 // This method is called when opening an URL with custom schemes.
141 - (BOOL)application:(UIApplication*)application
142 openURL:(NSURL*)url
143 options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options {
144 if ([self.lifeCycleDelegate application:application openURL:url options:options]) {
145 return YES;
146 }
147
148 // Relaying to the system here will case an infinite loop, so we don't do it here.
149 return [self handleOpenURL:url options:options relayToSystemIfUnhandled:NO];
150 }
151
152 // Helper function for opening an URL, either with a custom scheme or a http/https scheme.
153 - (BOOL)handleOpenURL:(NSURL*)url
154 options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options
155 relayToSystemIfUnhandled:(BOOL)throwBack {
156 if (![self isFlutterDeepLinkingEnabled]) {
157 return NO;
158 }
159
160 FlutterViewController* flutterViewController = [self rootFlutterViewController];
161 if (flutterViewController) {
162 [flutterViewController sendDeepLinkToFramework:url
163 completionHandler:^(BOOL success) {
164 if (!success && throwBack) {
165 // throw it back to iOS
166 [UIApplication.sharedApplication openURL:url];
167 }
168 }];
169 } else {
170 FML_LOG(ERROR) << "Attempting to open an URL without a Flutter RootViewController.";
171 return NO;
172 }
173 return YES;
174 }
175
176 - (BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)url {
177 return [self.lifeCycleDelegate application:application handleOpenURL:url];
178 }
179
180 - (BOOL)application:(UIApplication*)application
181 openURL:(NSURL*)url
182 sourceApplication:(NSString*)sourceApplication
183 annotation:(id)annotation {
184 return [self.lifeCycleDelegate application:application
185 openURL:url
186 sourceApplication:sourceApplication
187 annotation:annotation];
188 }
189
190 - (void)application:(UIApplication*)application
191 performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
192 completionHandler:(void (^)(BOOL succeeded))completionHandler {
193 [self.lifeCycleDelegate application:application
194 performActionForShortcutItem:shortcutItem
195 completionHandler:completionHandler];
196 }
197
198 - (void)application:(UIApplication*)application
199 handleEventsForBackgroundURLSession:(nonnull NSString*)identifier
200 completionHandler:(nonnull void (^)())completionHandler {
201 [self.lifeCycleDelegate application:application
202 handleEventsForBackgroundURLSession:identifier
203 completionHandler:completionHandler];
204 }
205
206 // This method is called when opening an URL with a http/https scheme.
207 - (BOOL)application:(UIApplication*)application
208 continueUserActivity:(NSUserActivity*)userActivity
209 restorationHandler:
210 (void (^)(NSArray<id<UIUserActivityRestoring>>* __nullable restorableObjects))
211 restorationHandler {
212 if ([self.lifeCycleDelegate application:application
213 continueUserActivity:userActivity
214 restorationHandler:restorationHandler]) {
215 return YES;
216 }
217
218 return [self handleOpenURL:userActivity.webpageURL options:@{} relayToSystemIfUnhandled:YES];
219 }
220
221 #pragma mark - FlutterPluginRegistry methods. All delegating to the rootViewController
222
223 - (NSObject<FlutterPluginRegistrar>*)registrarForPlugin:(NSString*)pluginKey {
224 FlutterViewController* flutterRootViewController = [self rootFlutterViewController];
225 if (flutterRootViewController) {
226 return [[flutterRootViewController pluginRegistry] registrarForPlugin:pluginKey];
227 }
228 return nil;
229 }
230
231 - (BOOL)hasPlugin:(NSString*)pluginKey {
232 FlutterViewController* flutterRootViewController = [self rootFlutterViewController];
233 if (flutterRootViewController) {
234 return [[flutterRootViewController pluginRegistry] hasPlugin:pluginKey];
235 }
236 return false;
237 }
238
239 - (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey {
240 FlutterViewController* flutterRootViewController = [self rootFlutterViewController];
241 if (flutterRootViewController) {
242 return [[flutterRootViewController pluginRegistry] valuePublishedByPlugin:pluginKey];
243 }
244 return nil;
245 }
246
247 #pragma mark - Selectors handling
248
249 - (void)addApplicationLifeCycleDelegate:(NSObject<FlutterApplicationLifeCycleDelegate>*)delegate {
250 [self.lifeCycleDelegate addDelegate:delegate];
251 }
252
253 #pragma mark - UIApplicationDelegate method dynamic implementation
254
255 - (BOOL)respondsToSelector:(SEL)selector {
256 if ([self.lifeCycleDelegate isSelectorAddedDynamically:selector]) {
257 return [self delegateRespondsSelectorToPlugins:selector];
258 }
259 return [super respondsToSelector:selector];
260 }
261
262 - (BOOL)delegateRespondsSelectorToPlugins:(SEL)selector {
263 if ([self.lifeCycleDelegate hasPluginThatRespondsToSelector:selector]) {
264 return [self.lifeCycleDelegate respondsToSelector:selector];
265 } else {
266 return NO;
267 }
268 }
269
270 - (id)forwardingTargetForSelector:(SEL)aSelector {
271 if ([self.lifeCycleDelegate isSelectorAddedDynamically:aSelector]) {
272 [self logCapabilityConfigurationWarningIfNeeded:aSelector];
273 return self.lifeCycleDelegate;
274 }
275 return [super forwardingTargetForSelector:aSelector];
276 }
277
278 // Mimic the logging from Apple when the capability is not set for the selectors.
279 // However the difference is that Apple logs these message when the app launches, we only
280 // log it when the method is invoked. We can possibly also log it when the app launches, but
281 // it will cause an additional scan over all the plugins.
282 - (void)logCapabilityConfigurationWarningIfNeeded:(SEL)selector {
283 NSArray* backgroundModesArray =
284 [[NSBundle mainBundle] objectForInfoDictionaryKey:kUIBackgroundMode];
285 NSSet* backgroundModesSet = [[NSSet alloc] initWithArray:backgroundModesArray];
286 if (selector == @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:)) {
287 if (![backgroundModesSet containsObject:kRemoteNotificationCapabitiliy]) {
288 NSLog(
289 @"You've implemented -[<UIApplicationDelegate> "
290 @"application:didReceiveRemoteNotification:fetchCompletionHandler:], but you still need "
291 @"to add \"remote-notification\" to the list of your supported UIBackgroundModes in your "
292 @"Info.plist.");
293 }
294 } else if (selector == @selector(application:performFetchWithCompletionHandler:)) {
295 if (![backgroundModesSet containsObject:kBackgroundFetchCapatibility]) {
296 NSLog(@"You've implemented -[<UIApplicationDelegate> "
297 @"application:performFetchWithCompletionHandler:], but you still need to add \"fetch\" "
298 @"to the list of your supported UIBackgroundModes in your Info.plist.");
299 }
300 }
301 }
302
303 #pragma mark - State Restoration
304
305 - (BOOL)application:(UIApplication*)application shouldSaveApplicationState:(NSCoder*)coder {
306 [coder encodeInt64:self.lastAppModificationTime forKey:kRestorationStateAppModificationKey];
307 return YES;
308 }
309
310 - (BOOL)application:(UIApplication*)application shouldRestoreApplicationState:(NSCoder*)coder {
311 int64_t stateDate = [coder decodeInt64ForKey:kRestorationStateAppModificationKey];
312 return self.lastAppModificationTime == stateDate;
313 }
314
315 - (BOOL)application:(UIApplication*)application shouldSaveSecureApplicationState:(NSCoder*)coder {
316 [coder encodeInt64:self.lastAppModificationTime forKey:kRestorationStateAppModificationKey];
317 return YES;
318 }
319
320 - (BOOL)application:(UIApplication*)application
321 shouldRestoreSecureApplicationState:(NSCoder*)coder {
322 int64_t stateDate = [coder decodeInt64ForKey:kRestorationStateAppModificationKey];
323 return self.lastAppModificationTime == stateDate;
324 }
325
326 - (int64_t)lastAppModificationTime {
327 NSDate* fileDate;
328 NSError* error = nil;
329 [[[NSBundle mainBundle] executableURL] getResourceValue:&fileDate
330 forKey:NSURLContentModificationDateKey
331 error:&error];
332 NSAssert(error == nil, @"Cannot obtain modification date of main bundle: %@", error);
333 return [fileDate timeIntervalSince1970];
334 }
335
336 @end
FlutterViewController
Definition: FlutterViewController.h:57
kRestorationStateAppModificationKey
static NSString *const kRestorationStateAppModificationKey
Definition: FlutterAppDelegate.mm:19
FlutterEngine_Internal.h
kUIBackgroundMode
static FLUTTER_ASSERT_ARC NSString *const kUIBackgroundMode
Definition: FlutterAppDelegate.mm:16
FlutterPluginAppLifeCycleDelegate.h
FlutterPluginRegistrar-p
Definition: FlutterPlugin.h:283
FlutterPluginAppLifeCycleDelegate
Definition: FlutterPluginAppLifeCycleDelegate.h:16
FlutterApplicationLifeCycleDelegate-p
Definition: FlutterPlugin.h:25
FlutterAppDelegate
Definition: FlutterAppDelegate.h:27
FlutterPluginAppLifeCycleDelegate_internal.h
FlutterAppDelegate.h
-[FlutterViewController pluginRegistry]
id< FlutterPluginRegistry > pluginRegistry()
Definition: FlutterViewController.mm:2375
FlutterAppDelegate(Test)::rootFlutterViewControllerGetter
FlutterViewController *(^ rootFlutterViewControllerGetter)(void)
FlutterAppDelegate_Test.h
kBackgroundFetchCapatibility
static NSString *const kBackgroundFetchCapatibility
Definition: FlutterAppDelegate.mm:18
FLUTTER_ASSERT_ARC
Definition: FlutterChannelKeyResponder.mm:13
FlutterViewController.h
kRemoteNotificationCapabitiliy
static NSString *const kRemoteNotificationCapabitiliy
Definition: FlutterAppDelegate.mm:17