diff --git a/packages/firebase_messaging/firebase_messaging/example/ios/Runner/AppDelegate.h b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/AppDelegate.h index 36e21bbf9cf4..01e6e1d4793a 100644 --- a/packages/firebase_messaging/firebase_messaging/example/ios/Runner/AppDelegate.h +++ b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/AppDelegate.h @@ -1,6 +1,6 @@ #import #import -@interface AppDelegate : FlutterAppDelegate +@interface AppDelegate : FlutterAppDelegate @end diff --git a/packages/firebase_messaging/firebase_messaging/example/ios/Runner/AppDelegate.m b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/AppDelegate.m index 70e83933db14..e243e6627852 100644 --- a/packages/firebase_messaging/firebase_messaging/example/ios/Runner/AppDelegate.m +++ b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/AppDelegate.m @@ -5,9 +5,13 @@ @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - [GeneratedPluginRegistrant registerWithRegistry:self]; + // [GeneratedPluginRegistrant registerWithRegistry:self]; // Override point for customization after application launch. return [super application:application didFinishLaunchingWithOptions:launchOptions]; } +- (void)didInitializeImplicitFlutterEngine:(NSObject *)engineBridge { + [GeneratedPluginRegistrant registerWithRegistry:engineBridge.pluginRegistry]; +} + @end diff --git a/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Info.plist b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Info.plist index b8293061bd33..cd5c989988e6 100644 --- a/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Info.plist +++ b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Info.plist @@ -50,5 +50,26 @@ UIApplicationSupportsIndirectInputEvents + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneDelegateClassName + FlutterSceneDelegate + UISceneConfigurationName + flutter + UISceneStoryboardFile + Main + + + + diff --git a/packages/firebase_messaging/firebase_messaging/example/lib/main.dart b/packages/firebase_messaging/firebase_messaging/example/lib/main.dart index 112a02db5783..128d68951a0b 100644 --- a/packages/firebase_messaging/firebase_messaging/example/lib/main.dart +++ b/packages/firebase_messaging/firebase_messaging/example/lib/main.dart @@ -93,6 +93,7 @@ Future setupFlutterNotifications() async { } void showFlutterNotification(RemoteMessage message) { + print('foreground message received: ${message.messageId}'); RemoteNotification? notification = message.notification; AndroidNotification? android = message.notification?.android; if (notification != null && android != null && !kIsWeb) { @@ -179,14 +180,17 @@ class _Application extends State { void initState() { super.initState(); - FirebaseMessaging.instance.getInitialMessage().then( - (value) => setState( - () { - _resolved = true; - initialMessage = value?.data.toString(); - }, - ), - ); + // Delay getInitialMessage call by 3 seconds + Future.delayed(const Duration(seconds: 3), () { + FirebaseMessaging.instance.getInitialMessage().then( + (value) => setState( + () { + _resolved = true; + initialMessage = value?.data.toString(); + }, + ), + ); + }); FirebaseMessaging.onMessage.listen(showFlutterNotification); diff --git a/packages/firebase_messaging/firebase_messaging/ios/firebase_messaging/Sources/firebase_messaging/FLTFirebaseMessagingPlugin.m b/packages/firebase_messaging/firebase_messaging/ios/firebase_messaging/Sources/firebase_messaging/FLTFirebaseMessagingPlugin.m index 7af15f16070f..8a89462afbe6 100644 --- a/packages/firebase_messaging/firebase_messaging/ios/firebase_messaging/Sources/firebase_messaging/FLTFirebaseMessagingPlugin.m +++ b/packages/firebase_messaging/firebase_messaging/ios/firebase_messaging/Sources/firebase_messaging/FLTFirebaseMessagingPlugin.m @@ -40,6 +40,9 @@ @implementation FLTFirebaseMessagingPlugin { NSString *_notificationOpenedAppID; NSString *_foregroundUniqueIdentifier; + // Track if scene delegate connected (for iOS 13+ scene delegate support) + BOOL _sceneDidConnect; + #ifdef __FF_NOTIFICATIONS_SUPPORTED_PLATFORM API_AVAILABLE(ios(10), macosx(10.14)) __weak id _originalNotificationCenterDelegate; @@ -59,6 +62,7 @@ - (instancetype)initWithFlutterMethodChannel:(FlutterMethodChannel *)channel self = [super init]; if (self) { _initialNotificationGathered = NO; + _sceneDidConnect = NO; _channel = channel; _registrar = registrar; // Application @@ -216,22 +220,31 @@ - (void)messaging:(nonnull FIRMessaging *)messaging #pragma mark - NSNotificationCenter Observers -- (void)application_onDidFinishLaunchingNotification:(nonnull NSNotification *)notification { - // Setup UIApplicationDelegate. -#if TARGET_OS_OSX - NSDictionary *remoteNotification = notification.userInfo[NSApplicationLaunchUserNotificationKey]; -#else - NSDictionary *remoteNotification = - notification.userInfo[UIApplicationLaunchOptionsRemoteNotificationKey]; -#endif +- (void)setupNotificationHandlingWithRemoteNotification: + (nullable NSDictionary *)remoteNotification { if (remoteNotification != nil) { // If remoteNotification exists, it is the notification that opened the app. _initialNotification = [FLTFirebaseMessagingPlugin remoteMessageUserInfoToDict:remoteNotification]; _initialNotificationID = remoteNotification[@"gcm.message_id"]; + _initialNotificationGathered = YES; + [self initialNotificationCallback]; + } else if (_sceneDidConnect) { + // For scene delegates, if no notification was found in connectionOptions, + // delay marking as gathered to allow didReceiveRemoteNotification to fire first + // for contentAvailable notifications that caused the app to launch + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), + dispatch_get_main_queue(), ^{ + if (!self->_initialNotificationGathered) { + self->_initialNotificationGathered = YES; + [self initialNotificationCallback]; + } + }); + } else { + // For non-scene delegate apps, mark as gathered immediately + _initialNotificationGathered = YES; + [self initialNotificationCallback]; } - _initialNotificationGathered = YES; - [self initialNotificationCallback]; [GULAppDelegateSwizzler registerAppDelegateInterceptor:self]; [GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods]; @@ -314,6 +327,17 @@ - (void)application_onDidFinishLaunchingNotification:(nonnull NSNotification *)n #endif } +- (void)application_onDidFinishLaunchingNotification:(nonnull NSNotification *)notification { + // Setup UIApplicationDelegate. +#if TARGET_OS_OSX + NSDictionary *remoteNotification = notification.userInfo[NSApplicationLaunchUserNotificationKey]; +#else + NSDictionary *remoteNotification = + notification.userInfo[UIApplicationLaunchOptionsRemoteNotificationKey]; +#endif + [self setupNotificationHandlingWithRemoteNotification:remoteNotification]; +} + #pragma mark - UNUserNotificationCenter Delegate Methods #ifdef __FF_NOTIFICATIONS_SUPPORTED_PLATFORM @@ -473,6 +497,15 @@ - (BOOL)application:(UIApplication *)application [FLTFirebaseMessagingPlugin remoteMessageUserInfoToDict:userInfo]; // Only handle notifications from FCM. if (userInfo[@"gcm.message_id"]) { + // For scene delegate apps: if this notification arrives during cold launch + // (before initial notification gathering is complete) and no notification was found + // in connectionOptions, this is the notification that caused the launch. + if (_sceneDidConnect && !_initialNotificationGathered && _initialNotification == nil) { + _initialNotification = notificationDict; + _initialNotificationID = userInfo[@"gcm.message_id"]; + _initialNotificationGathered = YES; + [self initialNotificationCallback]; + } if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) { __block BOOL completed = NO; @@ -538,24 +571,21 @@ - (BOOL)application:(UIApplication *)application #pragma mark - SceneDelegate Methods #if !TARGET_OS_OSX -- (BOOL)scene:(UIScene *)scene +- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions { // Handle launch notification if present - NSDictionary *remoteNotification = - connectionOptions.notificationResponse.notification.request.content.userInfo; - if (remoteNotification != nil) { - // If remoteNotification exists, it is the notification that opened the app. - _initialNotification = - [FLTFirebaseMessagingPlugin remoteMessageUserInfoToDict:remoteNotification]; - _initialNotificationID = remoteNotification[@"gcm.message_id"]; + // With scene delegates, the notification can be in notificationResponse if user tapped it + _sceneDidConnect = YES; + + NSDictionary *remoteNotification = nil; + if (connectionOptions.notificationResponse != nil) { + // User tapped the notification. + remoteNotification = + connectionOptions.notificationResponse.notification.request.content.userInfo; } - // Register for remote notifications in scene delegate - // This is critical for getting APNS token when using UISceneDelegate - [[UIApplication sharedApplication] registerForRemoteNotifications]; - - return YES; + [self setupNotificationHandlingWithRemoteNotification:remoteNotification]; } #endif