FCM pushes reach the Android build of a Flutter app using firebase_messaging but never the iOS build; what do you check?
answer
- APNs sits under FCM on Apple
- .p8 key uploaded to Firebase
- Push Notifications capability
- Background fetch + Remote notifications
- foreground presentation options
basics
~20 sCheck the APNs authentication key uploaded to Firebase, the Push Notifications capability and the Background fetch and Remote notifications modes in Xcode, granted permission, and an APNs token before getToken(). In the foreground, iOS shows nothing until presentation options are set.
solid answer
~40 sOn Apple platforms FCM delivers through APNs, so iOS needs setup Android does not. In the Firebase console, under Project Settings, Cloud Messaging, upload an **APNs authentication key** (`.p8`) with its key ID and team ID. In Xcode, enable the **Push Notifications** capability and the **Background fetch** and **Remote notifications** background modes, and keep method swizzling on, because the plugin requires it. In the app, call `requestPermission()` and check for `authorized` or `provisional`. Make sure `getAPNSToken()` is non-null before `getToken()`, which otherwise throws `apns-token-not-set`. If the only missing case is the **foreground**, that is the default: iOS shows no banner until `setForegroundNotificationPresentationOptions(alert: true, ...)`, though `onMessage` still fires. Finally, an app swiped away from the app switcher gets no background messages until it is reopened.
code
dart · 20 linesimport 'package:firebase_messaging/firebase_messaging.dart';
Future<void> diagnoseIosPush() async {
final messaging = FirebaseMessaging.instance;
final settings = await messaging.requestPermission();
print('permission: ${settings.authorizationStatus}');
final apns = await messaging.getAPNSToken();
print('APNs token present: ${apns != null}');
if (apns == null) return; // capability, entitlement or timing problem
print('FCM token: ${await messaging.getToken()}');
// Show order-shipped banners even while the app is open.
await messaging.setForegroundNotificationPresentationOptions(
alert: true,
badge: true,
sound: true,
);
}go deeper
Know the iOS-only setup pieces: an APNs key uploaded to Firebase, the Push Notifications capability, the two background modes, and requesting permission.
Explain why FCM on Apple depends on APNs, what apns-token-not-set means, and why foreground notifications show no banner unless presentation options are set.
Show a systematic diagnosis from credential to entitlement, permission, token and display, and recognise when a plugin upgrade for scene-based runners is the actual fix.
Treat push setup as release infrastructure: key custody, per-environment keys and a device-level smoke test in the release checklist, so iOS push cannot regress unnoticed.
## Why iOS has extra steps On Android, FCM delivers straight to the device. On Apple platforms, Firebase Cloud Messaging hands the message to **APNs**, Apple's push service, which delivers it. **firebase_messaging**, the FlutterFire plugin, can only receive what APNs agrees to deliver. APNs in turn needs three things: a credential from your project, entitlements in the app, and a device registration. A missing piece usually fails silently: Android works and iOS gets nothing. ## 1. Credentials in the Firebase console - Create an **APNs authentication key** in the Apple Developer account if you do not have one. - In the Firebase console, open **Project Settings**, then **Cloud Messaging**, and upload the `.p8` file with its **key ID** and your **team ID**. - Keys can be uploaded for development and for production; at least one is required. The `GoogleService-Info.plist` from `flutterfire configure` identifies the app, but it is **not** an APNs credential. ## 2. Capabilities in Xcode Open `ios/Runner.xcworkspace` and, on the Runner target: 1. Add the **Push Notifications** capability. 2. Under **Background Modes**, enable **Background fetch** and **Remote notifications**. 3. Keep **method swizzling** enabled. The FlutterFire guide states it is required: without it, FCM token handling does not work properly. ## 3. Runtime: permission and tokens - Call `FirebaseMessaging.instance.requestPermission()` and check `authorizationStatus`. `denied` means nothing will be shown, and the user must change it in Settings. - FCM on iOS needs the **APNs token** before it can issue an FCM token. The plugin checks `getAPNSToken()` before `getToken()`, `deleteToken()` and `subscribeToTopic()`, and throws a `FirebaseException` with code `apns-token-not-set` when it is `null`. If startup logs show that error, APNs registration has not completed. Usually the capability or entitlement is missing, or the call simply came too early and needs a retry. - Confirm the token you test with is the one the device currently reports. A reinstall can produce a new one. ## 4. "It arrives, but I don't see it" Some reports of "never arrives" are really about display: | Situation | Default behaviour | What to do | |---|---|---| | App in foreground, notification message | no banner; `onMessage` emits | `setForegroundNotificationPresentationOptions(alert: true, badge: true, sound: true)` | | Options set once, call later removed | options still apply (persisted) | call again with `alert: false` to turn off | | App swiped away in the app switcher | no background messages | user must reopen the app | | Many pushes in a short time | iOS may throttle | test at realistic rates | The presentation options only change display. The user's permission settings take priority over them. ## 5. Plugin version and app setup Recent releases fixed several iOS registration paths: - 16.1.0 added support for apps that use the scene-based lifecycle (UIScene). - 16.5.0 added early notification-delegate setup for those apps. - 16.7.0 registers for APNs after Dart's `Firebase.initializeApp()`, and also when scene-based plugins miss launch callbacks. If the project's iOS runner was migrated to scenes and pushes stopped, upgrading firebase_messaging is a legitimate step, not a guess. ## A short checklist - APNs `.p8` key uploaded, with the key ID and team ID matching the Apple account that signs the app. - Push Notifications capability, and the Background fetch and Remote notifications modes, on the Runner target. - Swizzling not disabled. - Permission `authorized` or `provisional`. - `getAPNSToken()` non-null, then `getToken()` succeeds and the token reaches your backend. - Foreground display configured, or handled in `onMessage`. - Tested on a device with the app opened at least once and not swiped away.
- You enabled foreground banners with setForegroundNotificationPresentationOptions, then deleted the call. Why do banners still appear?The plugin persists options set to `true`, and removing the call does not reset them. To stop foreground banners, call the method again with `alert: false` (and `badge`/`sound` as needed). The user's own notification settings still take priority either way.
- Why does iOS need a Firebase-side credential when Android does not?On Apple platforms FCM forwards messages to APNs, and APNs accepts them only from a sender that holds the app's APNs authentication key. Uploading the `.p8` key, with its key ID and team ID, lets Firebase authenticate to APNs on your behalf. Android delivery needs no equivalent key.
saying these in an interview costs you the question
- GoogleService-Info.plist alone is enough for FCM to reach iOS devices.
- The Push Notifications capability replaces the need for an APNs key.
- If onMessage fires, iOS will also show a banner in the foreground.
- Removing setForegroundNotificationPresentationOptions resets foreground display.
- An app swiped away from the app switcher still gets background messages.