Skip to content

Commit 6fd8929

Browse files
authored
fix(messaging, iOS): refactor notification handling in scene delegate methods (#17905)
* fix(firebase_messaging): enhance iOS scene delegate support and improve notification handling * fix(firebase_messaging): refactor notification handling in scene delegate methods * fix(firebase_messaging): improve scene delegate support for notification handling * chore: remove redundant plugin registration in AppDelegate
1 parent c0e682e commit 6fd8929

File tree

5 files changed

+91
-33
lines changed

5 files changed

+91
-33
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#import <Flutter/Flutter.h>
22
#import <UIKit/UIKit.h>
33

4-
@interface AppDelegate : FlutterAppDelegate
4+
@interface AppDelegate : FlutterAppDelegate <FlutterImplicitEngineDelegate>
55

66
@end

packages/firebase_messaging/firebase_messaging/example/ios/Runner/AppDelegate.m

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ @implementation AppDelegate
55

66
- (BOOL)application:(UIApplication *)application
77
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
8-
[GeneratedPluginRegistrant registerWithRegistry:self];
98
// Override point for customization after application launch.
109
return [super application:application didFinishLaunchingWithOptions:launchOptions];
1110
}
1211

12+
- (void)didInitializeImplicitFlutterEngine:(NSObject<FlutterImplicitEngineBridge> *)engineBridge {
13+
[GeneratedPluginRegistrant registerWithRegistry:engineBridge.pluginRegistry];
14+
}
15+
1316
@end

packages/firebase_messaging/firebase_messaging/example/ios/Runner/Info.plist

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,26 @@
5050
<true/>
5151
<key>UIApplicationSupportsIndirectInputEvents</key>
5252
<true/>
53+
<key>UIApplicationSceneManifest</key>
54+
<dict>
55+
<key>UIApplicationSupportsMultipleScenes</key>
56+
<false/>
57+
<key>UISceneConfigurations</key>
58+
<dict>
59+
<key>UIWindowSceneSessionRoleApplication</key>
60+
<array>
61+
<dict>
62+
<key>UISceneClassName</key>
63+
<string>UIWindowScene</string>
64+
<key>UISceneDelegateClassName</key>
65+
<string>FlutterSceneDelegate</string>
66+
<key>UISceneConfigurationName</key>
67+
<string>flutter</string>
68+
<key>UISceneStoryboardFile</key>
69+
<string>Main</string>
70+
</dict>
71+
</array>
72+
</dict>
73+
</dict>
5374
</dict>
5475
</plist>

packages/firebase_messaging/firebase_messaging/example/lib/main.dart

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ Future<void> setupFlutterNotifications() async {
9393
}
9494

9595
void showFlutterNotification(RemoteMessage message) {
96+
print('foreground message received: ${message.messageId}');
9697
RemoteNotification? notification = message.notification;
9798
AndroidNotification? android = message.notification?.android;
9899
if (notification != null && android != null && !kIsWeb) {
@@ -179,14 +180,17 @@ class _Application extends State<Application> {
179180
void initState() {
180181
super.initState();
181182

182-
FirebaseMessaging.instance.getInitialMessage().then(
183-
(value) => setState(
184-
() {
185-
_resolved = true;
186-
initialMessage = value?.data.toString();
187-
},
188-
),
189-
);
183+
// Delay getInitialMessage call by 3 seconds
184+
Future.delayed(const Duration(seconds: 3), () {
185+
FirebaseMessaging.instance.getInitialMessage().then(
186+
(value) => setState(
187+
() {
188+
_resolved = true;
189+
initialMessage = value?.data.toString();
190+
},
191+
),
192+
);
193+
});
190194

191195
FirebaseMessaging.onMessage.listen(showFlutterNotification);
192196

packages/firebase_messaging/firebase_messaging/ios/firebase_messaging/Sources/firebase_messaging/FLTFirebaseMessagingPlugin.m

Lines changed: 53 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ @implementation FLTFirebaseMessagingPlugin {
4040
NSString *_notificationOpenedAppID;
4141
NSString *_foregroundUniqueIdentifier;
4242

43+
// Track if scene delegate connected (for iOS 13+ scene delegate support)
44+
BOOL _sceneDidConnect;
45+
4346
#ifdef __FF_NOTIFICATIONS_SUPPORTED_PLATFORM
4447
API_AVAILABLE(ios(10), macosx(10.14))
4548
__weak id<UNUserNotificationCenterDelegate> _originalNotificationCenterDelegate;
@@ -59,6 +62,7 @@ - (instancetype)initWithFlutterMethodChannel:(FlutterMethodChannel *)channel
5962
self = [super init];
6063
if (self) {
6164
_initialNotificationGathered = NO;
65+
_sceneDidConnect = NO;
6266
_channel = channel;
6367
_registrar = registrar;
6468
// Application
@@ -216,22 +220,31 @@ - (void)messaging:(nonnull FIRMessaging *)messaging
216220

217221
#pragma mark - NSNotificationCenter Observers
218222

219-
- (void)application_onDidFinishLaunchingNotification:(nonnull NSNotification *)notification {
220-
// Setup UIApplicationDelegate.
221-
#if TARGET_OS_OSX
222-
NSDictionary *remoteNotification = notification.userInfo[NSApplicationLaunchUserNotificationKey];
223-
#else
224-
NSDictionary *remoteNotification =
225-
notification.userInfo[UIApplicationLaunchOptionsRemoteNotificationKey];
226-
#endif
223+
- (void)setupNotificationHandlingWithRemoteNotification:
224+
(nullable NSDictionary *)remoteNotification {
227225
if (remoteNotification != nil) {
228226
// If remoteNotification exists, it is the notification that opened the app.
229227
_initialNotification =
230228
[FLTFirebaseMessagingPlugin remoteMessageUserInfoToDict:remoteNotification];
231229
_initialNotificationID = remoteNotification[@"gcm.message_id"];
230+
_initialNotificationGathered = YES;
231+
[self initialNotificationCallback];
232+
} else if (_sceneDidConnect) {
233+
// For scene delegates, if no notification was found in connectionOptions,
234+
// delay marking as gathered to allow didReceiveRemoteNotification to fire first
235+
// for contentAvailable notifications that caused the app to launch
236+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)),
237+
dispatch_get_main_queue(), ^{
238+
if (!self->_initialNotificationGathered) {
239+
self->_initialNotificationGathered = YES;
240+
[self initialNotificationCallback];
241+
}
242+
});
243+
} else {
244+
// For non-scene delegate apps, mark as gathered immediately
245+
_initialNotificationGathered = YES;
246+
[self initialNotificationCallback];
232247
}
233-
_initialNotificationGathered = YES;
234-
[self initialNotificationCallback];
235248

236249
[GULAppDelegateSwizzler registerAppDelegateInterceptor:self];
237250
[GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods];
@@ -314,6 +327,17 @@ - (void)application_onDidFinishLaunchingNotification:(nonnull NSNotification *)n
314327
#endif
315328
}
316329

330+
- (void)application_onDidFinishLaunchingNotification:(nonnull NSNotification *)notification {
331+
// Setup UIApplicationDelegate.
332+
#if TARGET_OS_OSX
333+
NSDictionary *remoteNotification = notification.userInfo[NSApplicationLaunchUserNotificationKey];
334+
#else
335+
NSDictionary *remoteNotification =
336+
notification.userInfo[UIApplicationLaunchOptionsRemoteNotificationKey];
337+
#endif
338+
[self setupNotificationHandlingWithRemoteNotification:remoteNotification];
339+
}
340+
317341
#pragma mark - UNUserNotificationCenter Delegate Methods
318342

319343
#ifdef __FF_NOTIFICATIONS_SUPPORTED_PLATFORM
@@ -473,6 +497,15 @@ - (BOOL)application:(UIApplication *)application
473497
[FLTFirebaseMessagingPlugin remoteMessageUserInfoToDict:userInfo];
474498
// Only handle notifications from FCM.
475499
if (userInfo[@"gcm.message_id"]) {
500+
// For scene delegate apps: if this notification arrives during cold launch
501+
// (before initial notification gathering is complete) and no notification was found
502+
// in connectionOptions, this is the notification that caused the launch.
503+
if (_sceneDidConnect && !_initialNotificationGathered && _initialNotification == nil) {
504+
_initialNotification = notificationDict;
505+
_initialNotificationID = userInfo[@"gcm.message_id"];
506+
_initialNotificationGathered = YES;
507+
[self initialNotificationCallback];
508+
}
476509
if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
477510
__block BOOL completed = NO;
478511

@@ -538,24 +571,21 @@ - (BOOL)application:(UIApplication *)application
538571
#pragma mark - SceneDelegate Methods
539572

540573
#if !TARGET_OS_OSX
541-
- (BOOL)scene:(UIScene *)scene
574+
- (void)scene:(UIScene *)scene
542575
willConnectToSession:(UISceneSession *)session
543576
options:(UISceneConnectionOptions *)connectionOptions {
544577
// Handle launch notification if present
545-
NSDictionary *remoteNotification =
546-
connectionOptions.notificationResponse.notification.request.content.userInfo;
547-
if (remoteNotification != nil) {
548-
// If remoteNotification exists, it is the notification that opened the app.
549-
_initialNotification =
550-
[FLTFirebaseMessagingPlugin remoteMessageUserInfoToDict:remoteNotification];
551-
_initialNotificationID = remoteNotification[@"gcm.message_id"];
578+
// With scene delegates, the notification can be in notificationResponse if user tapped it
579+
_sceneDidConnect = YES;
580+
581+
NSDictionary *remoteNotification = nil;
582+
if (connectionOptions.notificationResponse != nil) {
583+
// User tapped the notification.
584+
remoteNotification =
585+
connectionOptions.notificationResponse.notification.request.content.userInfo;
552586
}
553587

554-
// Register for remote notifications in scene delegate
555-
// This is critical for getting APNS token when using UISceneDelegate
556-
[[UIApplication sharedApplication] registerForRemoteNotifications];
557-
558-
return YES;
588+
[self setupNotificationHandlingWithRemoteNotification:remoteNotification];
559589
}
560590
#endif
561591

0 commit comments

Comments
 (0)