[firebase_messaging][iOS] 16.7.0 breaks foreground notifications posted by other plugins on UIScene apps that own the UNUserNotificationCenter delegate
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
- 難易度
- 3/5
- 見積もり時間
- 1〜2日
- 初心者へのやさしさ
- 74/100
- issue の種類
- バグ
- 明瞭さ
- 明確に書かれている
- 活発さ
- 活発
- 技術スタック
- dart, objective-c, swift
- 領域
- mobile-dev
調査の方向性
Start at registerWithRegistrar and the scene-connect path in the iOS messaging plugin, then review registerForRemoteNotificationsIfAutoInitEnabled and configureNotificationCenterDelegate. Reproduce with ios/Runner/AppDelegate.swift and lib/main.dart in firebase_messaging/example using a data-only foreground message. Done means APNs registration still works and flutter_local_notifications displays the notification without firebase_messaging taking over the host delegate.
索引モデルが issue の本文から書いたものです。
説明
Is there an existing issue for this?
- I have searched the existing issues.
Which plugins are affected?
Messaging
Which platforms are affected?
iOS
Description
After upgrading firebase_messaging from 16.5.0 to 16.7.0, foreground notifications posted by flutter_local_notifications are no longer displayed on iOS.
This affects apps that:
- adopt the UIScene lifecycle (
UIApplicationSceneManifest+FlutterImplicitEngineDelegate), - assign
UNUserNotificationCenter.current().delegatethemselves (so thatFlutterAppDelegateforwardswillPresentNotificationto every plugin registered viaaddApplicationDelegate), and - render foreground notifications themselves from data-only messages, without calling
setForegroundNotificationPresentationOptions.
FlutterLocalNotificationsPlugin.show() completes normally, but nothing appears on screen.
This is a regression: 16.5.0 works, 16.7.0 does not. 16.6.0 contains no iOS notification changes.
Calling FLTFirebaseMessagingPlugin.configureNotificationCenterDelegate() as documented for UIScene apps does not help either (see the verification table below).
Root cause
#18620 added an early setup call in registerWithRegistrar:
if ([UIApplication sharedApplication].connectedScenes.count > 0) {
instance->_sceneDidConnect = YES;
[instance setupNotificationHandlingWithRemoteNotification:nil];
}
The intent was to request APNs registration when UIScene apps register the plugin after the launch callbacks have already fired. However, setupNotificationHandlingWithRemoteNotification: also calls configureNotificationCenterDelegate, which contains this guard:
if (currentDelegate != nil) {
if ([currentDelegate conformsToProtocol:@protocol(FlutterAppLifeCycleProvider)]) {
shouldReplaceDelegate = NO;
}
}
The guard only applies when a delegate is already installed. Depending on when the scene connects, plugin registration can run before the host assigns its delegate, so currentDelegate is nil, the guard is skipped, and this plugin takes over the delegate while storing nil in _originalNotificationCenterDelegate.
The host then overwrites the delegate, but this plugin remains in the forwarding chain via addApplicationDelegate. When a local notification is about to be presented, its willPresentNotification runs and reaches:
} else {
UNNotificationPresentationOptions presentationOptions = UNNotificationPresentationOptionNone;
NSDictionary *persistedOptions = [[NSUserDefaults standardUserDefaults]
dictionaryForKey:kMessagingPresentationOptionsUserDefaults];
if (persistedOptions != nil) { ... }
completionHandler(presentationOptions);
}
_originalNotificationCenterDelegate is nil and setForegroundNotificationPresentationOptions was never called, so it answers UNNotificationPresentationOptionNone and suppresses a notification that belongs to another plugin.
Reproducing the issue
Applied to firebase_messaging/example on main.
ios/Runner/AppDelegate.swift — keep the documented call and additionally let the host own the delegate:
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// UIScene registers plugins after this method returns. Apple requires the
// UNUserNotificationCenter delegate to be set before launch completes.
FLTFirebaseMessagingPlugin.configureNotificationCenterDelegate()
+
+ // Apps that also use flutter_local_notifications must own the delegate so
+ // that FlutterAppDelegate forwards willPresentNotification to every plugin
+ // registered through addApplicationDelegate.
+ UNUserNotificationCenter.current().delegate = self
+
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
lib/main.dart — initialize() is required before any notification can be shown on iOS, and the presentation options are removed:
- await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
- alert: true,
- badge: true,
- sound: true,
- );
+ // Required before any notification can be shown on iOS.
+ await flutterLocalNotificationsPlugin.initialize(
+ settings: const InitializationSettings(
+ android: AndroidInitializationSettings('launch_background'),
+ iOS: DarwinInitializationSettings(),
+ ),
+ );
+
+ // NOTE: setForegroundNotificationPresentationOptions is intentionally NOT
+ // called. Apps that render foreground notifications themselves with
+ // flutter_local_notifications do not need FCM to present anything.
Also make showFlutterNotification read title/body from message.data so it posts a notification for data-only messages on iOS.
Then send a data-only message while the app is in the foreground:
{
"message": {
"token": "<fcm-token>",
"data": { "title": "title", "body": "body" }
}
}
Result on 16.7.0
test: delegate after configureNotificationCenterDelegate = Optional(<FLTFirebaseMessagingPlugin: 0x30154ce00>)
test: delegate after didFinishLaunching = Optional(<Runner.AppDelegate: 0x301d44910>)
test: delegate before plugin registration = Optional(<Runner.AppDelegate: 0x301d44910>)
test: delegate after plugin registration = Optional(<Runner.AppDelegate: 0x301d44910>)
test: onMessage received (1789709030567108)
test: localNotification.show start
test: localNotification.show done
show() completes, but no notification is displayed.
Proposed fix
Request APNs registration without performing the delegate handover. registerForRemoteNotificationsIfAutoInitEnabled was added in #18650 and is documented as safe to call repeatedly:
if ([UIApplication sharedApplication].connectedScenes.count > 0) {
instance->_sceneDidConnect = YES;
- [instance setupNotificationHandlingWithRemoteNotification:nil];
+ [instance registerForRemoteNotificationsIfAutoInitEnabled];
}
Full notification handling then runs from didFinishLaunching / scene:willConnect as before, by which time the host delegate is installed and the FlutterAppLifeCycleProvider guard applies.
Verification
Same device, same message, using the example described above:
configureNotificationCenterDelegate() |
plugin | install | foreground notification | APNs token |
|---|---|---|---|---|
| not called | 16.7.0 as released | existing | not displayed | received |
| not called | 16.7.0 + patch | existing | displayed | received |
| called | 16.7.0 as released | existing | not displayed | received |
| called | 16.7.0 + patch | existing | displayed | received |
| called | 16.7.0 + patch | fresh (app deleted first) | displayed | received |
show() completed in every run; the only difference is the delegate path on the iOS side.
The last row matches the test plan of #18620 (app deleted from a physical device, no cached APNs token). getToken() resolved without apns-token-not-set, so the goal of #18620 — fixing #18555 — is preserved.
Firebase Core version
4.15.0
Flutter Version
3.44.9
Relevant Log Output
test: delegate after configureNotificationCenterDelegate = Optional(<FLTFirebaseMessagingPlugin: 0x30154ce00>)
test: delegate after didFinishLaunching = Optional(<Runner.AppDelegate: 0x301d44910>)
test: delegate before plugin registration = Optional(<Runner.AppDelegate: 0x301d44910>)
test: delegate after plugin registration = Optional(<Runner.AppDelegate: 0x301d44910>)
test: onMessage received (1789709030567108)
test: localNotification.show start
test: localNotification.show done
Flutter dependencies
firebase_core: ^4.15.0
firebase_messaging: ^16.7.0
flutter_local_notifications: ^21.0.0
Additional context and comments
Tested on iPhone SE (3rd generation), iOS 18.0.1, Flutter 3.44.9.
The same result occurs when FLTFirebaseMessagingPlugin.configureNotificationCenterDelegate() is not called and only UNUserNotificationCenter.current().delegate = self is set.
- 主要言語
- Dart
- スター
- 9.3k
- フォーク
- 4.1k
- 平均マージ
- 2日 6時間
- マージ済み PR(30日)
- 38
環境構築
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
firebase/flutterfire のほかの issue
-
Needs Attention plugin: auth reproduced type: bug
難易度 1/5 1時間未満 初心者へのやさしさ 92/100
firebase/flutterfire#18726 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
Needs Attention platform: windows plugin: ai type: documentation
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
firebase/flutterfire#18720 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
[cloud_firestore]: a snapshots() listener cancelled while it registers is never removed nativelyオープンNeeds Attention platform: android platform: ios platform: macos plugin: cloud_firestore reproduced type: bug
難易度 3/5 1〜2日 初心者へのやさしさ 72/100
firebase/flutterfire#18724 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
blocked: customer-response platform: web plugin: cloud_firestore type: bug
難易度 4/5 3〜5日 初心者へのやさしさ 48/100
firebase/flutterfire#18700 · コメント 2 件 ·
メンテナーはふだん 1 日以内に返信
-
blocked: customer-response platform: ios plugin: app_check Stale type: bug
難易度 4/5 3〜5日 初心者へのやさしさ 38/100
firebase/flutterfire#18665 · コメント 5 件 ·
メンテナーはふだん 1 日以内に返信
firebase/flutterfire の issue をすべて見る
似ている issue
-
component: genui P2 status: first-line-handled type: bug
難易度 1/5 1時間未満 初心者へのやさしさ 90/100
a2ui-project/a2ui#2854 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
google/webcrypto.dart#419 ·
メンテナーはふだん 4 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 84/100
openfoodfacts/smooth-app#7805 ·
メンテナーはふだん 1 日以内に返信
-
bug
難易度 1/5 1時間未満 初心者へのやさしさ 88/100
-
難易度 2/5 1〜3時間 初心者へのやさしさ 74/100
googleapis/google-cloud-dart#366 ·
メンテナーはふだん 1 日以内に返信