Your Flutter app gets push notifications while it's open, but nothing shows up once it's in the background or closed. This is one of the most common Firebase Cloud Messaging problems, and it almost always comes down to a handful of causes. Here they are, from most to least likely.
Quick checklist
- The background handler is a top-level function with
@pragma('vm:entry-point'). -
FirebaseMessaging.onBackgroundMessage()is registered beforerunApp(). - The message has a notification block, or is data-only with high priority (Android) and
content-available(iOS). - Notification permission is granted (Android 13+ and iOS both ask).
- On iOS: Push Notifications and Background Modes → Remote notifications are enabled, and the APNs key is uploaded to Firebase.
- You're testing on a real device in a release or profile build, and the app wasn't force-stopped.
1. Set up the background handler correctly
When your app isn't in the foreground, firebase_messaging runs your handler in a separate Dart isolate. That only works if Flutter can find the function by itself, so it has to be a top-level function (not inside a class) and marked as an entry point. Otherwise tree shaking in release builds can remove it, and messages silently do nothing.
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
// Must be a top-level function, not a class method or closure
@pragma('vm:entry-point')
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
// Runs in its own isolate, so initialise Firebase again here
await Firebase.initializeApp();
debugPrint('Background message: ${message.messageId}');
}
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
// Register before runApp()
FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);
runApp(const MyApp());
}
Because the handler runs in its own isolate, it can't touch your widgets, providers or in-memory state. If you use other Firebase services inside it, call Firebase.initializeApp() there too.
2. Send the right kind of message
FCM has two kinds of messages, and they behave very differently in the background:
- Notification messages (with a
notificationblock) are displayed by the operating system itself when your app is in the background or closed. Your Dart code doesn't have to run for the user to see them. This is what you want for anything user-facing. - Data-only messages (just a
datablock) display nothing on their own. They wake your background handler, and only if the platform agrees to deliver them.
For a notification the user must see, send something like this through the FCM HTTP v1 API:
{
"message": {
"token": "DEVICE_FCM_TOKEN",
"notification": {
"title": "Your order is out for delivery",
"body": "Arriving in about 20 minutes"
},
"data": { "orderId": "4821" },
"android": {
"priority": "high",
"notification": { "channel_id": "high_importance" }
},
"apns": {
"headers": { "apns-priority": "10" },
"payload": { "aps": { "sound": "default" } }
}
}
}
If you really need a silent data-only message, mark it high priority on Android and as a background push on iOS:
{
"message": {
"token": "DEVICE_FCM_TOKEN",
"data": { "type": "sync", "orderId": "4821" },
"android": { "priority": "high" },
"apns": {
"headers": { "apns-push-type": "background", "apns-priority": "5" },
"payload": { "aps": { "content-available": 1 } }
}
}
}
You can build and check both payloads with our free FCM payload builder, which also gives you the cURL command.
3. Ask for notification permission
Since Android 13, apps need the POST_NOTIFICATIONS runtime permission, and iOS has always needed one. Without it, messages are delivered to the device but never shown. Ask once, at a sensible moment:
final settings = await FirebaseMessaging.instance.requestPermission(
alert: true,
badge: true,
sound: true,
);
debugPrint('Permission: ${settings.authorizationStatus}');
If the status comes back as denied, the system won't show the dialog again, so point the user to the app's notification settings instead.
4. Android: battery savers and force stop
If notifications arrive on a Pixel but not on some Xiaomi, Oppo, Vivo, Realme, OnePlus or Samsung phones, the phone's battery optimisation is likely stopping your app in the background. Things to check:
- Force stop from Settings blocks every FCM message until the user opens the app again. A normal swipe from recents shouldn't, but some brands treat it like a force stop.
- Use high priority for time-sensitive messages. Normal priority can be delayed while the phone is in Doze mode.
- Create a notification channel with high importance and send its
channel_id, or the notification may arrive silently without a heads-up banner. - For testing, set the app's battery usage to Unrestricted and turn on Autostart where the phone has it. You can't do this for your users, but it confirms the cause.
5. iOS: capabilities, APNs and how the app was closed
- In Xcode, under Signing & Capabilities, add Push Notifications and Background Modes with Remote notifications ticked.
- Upload your APNs authentication key in the Firebase console (Project settings → Cloud Messaging). Without it, nothing reaches iOS at all. The Firebase iOS push guide walks through it.
- Background data messages are throttled by iOS and aren't delivered at all if the user swiped the app away. Notification messages are still shown in that case.
- If you see
apns-token-not-set, the device never got an APNs token. See how to fix the APNs token error.
6. Test the right way
Debug builds behave differently from what your users run. When you stop the app from your IDE, the process is killed in a way that real users never do, and background messages may not arrive. Test background and terminated states on a real device with flutter run --release (or a profile build), close the app the way a user would, then send a message.
To handle taps, use FirebaseMessaging.onMessageOpenedApp for a tap while the app is in the background, and FirebaseMessaging.instance.getInitialMessage() for a tap that launched the app from closed.
Want this handled for you?
The notifySphere Flutter SDK handles token registration and wires your background handler in at startup, and it tracks delivery and opens for every notification, so you can see whether a message reached the phone or was dropped on the way. It's free up to 1,000 monthly active users. Compare it with other options on our OneSignal alternatives page.
Frequently asked questions
Why do Flutter notifications work in the foreground but not in the background?
Usually one of four things: the background handler isn't a top-level function marked @pragma('vm:entry-point'), it's registered after runApp(), the message is data-only without high priority, or the phone's battery saver is stopping your app. Fix them in that order.
Why does onBackgroundMessage not get called on iOS?
For a data-only message, iOS needs content-available: 1 in the aps payload and the Background Modes capability with Remote notifications enabled. Even then iOS throttles background messages and won't deliver them if the user swiped the app away. For anything the user must see, send a notification payload instead.
Do push notifications arrive if the user force-stops or swipes away the app?
On Android, force stop from Settings blocks all FCM messages until the user opens the app again; a normal swipe from recents does not, except on some phone brands with aggressive battery savers. On iOS, notification messages are still shown after a swipe, but background data messages are not delivered.
Can I update the UI from onBackgroundMessage?
No. The background handler runs in a separate isolate with no access to your widgets or app state. Save what you need (for example with shared_preferences) and read it when the app opens, or use onMessageOpenedApp and getInitialMessage to react to taps.
Why are notifications not showing when the app is open?
That's expected. In the foreground, Android doesn't display FCM notifications on its own, so you show them yourself with flutter_local_notifications. On iOS, call setForegroundNotificationPresentationOptions(alert: true, badge: true, sound: true).
Originally published on the notifySphere blog. notifySphere is push notifications, WhatsApp Business API, deep linking, and in-app support in one SDK — free up to 1,000 users.
Top comments (0)