iosflutterpush-notificationfirebase-cloud-messagingapple-push-notifications

Flutter FCM FirebaseMessaging.onBackgroundMessage not called on iOS release mode (data-only silent push, capabilities enabled)


In a Flutter chat app using FCM data-only pushes with awesome_notifications for local notifications, FirebaseMessaging.onBackgroundMessage works perfectly on Android (all states) and iOS debug (foreground/background), but fails silently in iOS release when the app is backgrounded/killed.

Setup (verified working in debug) main_bootstrap.dart:

dart
await Firebase.initializeApp(options: firebaseOptions);
FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler); // Top-level handler with @pragma('vm:entry-point')
runApp(ProviderScope(child: MainApp()));

Xcode Capabilities (Release target):

✅ Push Notifications

✅ Background Modes → Remote notifications + Background fetch

FCM Payload (data-only, silent):

json
{
  "tokens": ["device_token"],
  "data": {
    "roomId": "123",
    "roomType": "group",
    "senderName": "Ahmed",
    "preview": "Hello there",
    "timestamp": "1735400000000"
  },
  "apns": {
    "payload": { "aps": { "content-available": 1 } },
    "headers": { "apns-push-type": "background", "apns-priority": "5" }
  }
}

Minimal test handler (still fails in iOS release background):

dart
@pragma('vm:entry-point')
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  await Firebase.initializeApp();
  print('🔥 BACKGROUND: ${message.messageId}'); // Never appears in Xcode logs
}

Questions: Is this an iOS release-specific limitation where silent pushes are throttled/dropped more aggressively than debug, even with correct content-available:1 + apns-push-type:background?

firebase_messaging version issue? Using latest firebase_messaging: ^15.0.0 - any known release build bugs?

What Xcode build settings differ between debug/release that could block background handler invocation?

Workaround for chat apps? Should I send dual payloads (silent for background handler + visible notification as fallback) or is there a reliable release pattern?


Solution

  • This is a hard space to get a good handle on.

    Using Flutter makes it harder because it tries to abstract a lot of the platform specific stuff away, but it still uses platform services.

    When you use FCM to message an iOS device, the delivery to the device and to your app are managed by Apples APNS (or it's sometimes called APS). So, server side, you can talk to FCM whether you're messaging Android or iOS. But when you're messaging an iOS device FCM hands it over to Apple's servers to deliver to their platform.

    iOS handles push messaging significantly differently from Android, particularly the silent / data only push messaging.

    Also, on iOS there's a significant difference between having your app backgrounded and swiped closed:

    The main differences we have found are:

    It's also an area where the terminology is dangerously overloaded. Apple calls their messaging "Notifications" even if there is no Notification element - if it's silent / data only, they still call it a "Notification". And there is a huge difference between what you can do with a locally created notification, vs what you can do with an FCM / remote notification. But the documentation on these two things just say "notification" and often don't distinguish between a local (awesome_notifications) notification and a remote (FCM) notification, which just adds another layer of complexity.

    This is Apple's documentation on what happens once FCM passes the message over to APNS for processing.

    https://developer.apple.com/documentation/usernotifications/pushing-background-updates-to-your-app

    For our use case (which is not a chat app) we are moving to always sending a data+notification message. Because it has that notification element it's more reliably delivered, particularly on iOS.

    That would probably be what I'd do for a chat app also, given that if your app is foreground the system notification is not created, even if you specify one. Whereas if it's backgrounded or swiped closed, you still get that system notification (just not the silent message).