Back to Blog
Flutter Development

Flutter Push Notifications with Firebase Cloud Messaging

End-to-end guide to push notifications in Flutter mobile apps using Firebase Cloud Messaging — setup for iOS and Android, foreground handling, deep links, and topic targeting.

D
Don Wilson
Mobile Developer
April 8, 2026
11 min read
Flutter Push Notifications with Firebase Cloud Messaging

Push notifications are the most powerful re-engagement tool a mobile app has. They are also one of the most error-prone features to ship. Firebase Cloud Messaging (FCM) makes them tractable across iOS and Android. This guide covers a real-world Flutter integration end-to-end.

Setting Up Firebase

Use the FlutterFire CLI — it generates the platform configs and Dart bindings for you:

dart pub global activate flutterfire_cli
flutterfire configure
flutter pub add firebase_core firebase_messaging flutter_local_notifications

iOS: APNs Configuration

  1. Enable Push Notifications and Background Modes (Remote notifications) in Xcode
  2. Create an APNs Auth Key in Apple Developer console (.p8 file)
  3. Upload the key to Firebase Console → Project Settings → Cloud Messaging
  4. Add aps-environment entitlement

Android: Defaults and Channels

Android 8+ requires notification channels. Create them once on first launch:

const channel = AndroidNotificationChannel(
  'high_importance_channel',
  'Important Notifications',
  description: 'Used for time-sensitive alerts.',
  importance: Importance.high,
);

await FlutterLocalNotificationsPlugin()
  .resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
  ?.createNotificationChannel(channel);

Requesting Permission

final messaging = FirebaseMessaging.instance;
final settings = await messaging.requestPermission(
  alert: true, badge: true, sound: true,
);
if (settings.authorizationStatus == AuthorizationStatus.authorized) {
  final token = await messaging.getToken();
  await uploadTokenToServer(token);
}

On Android 13+, also request POST_NOTIFICATIONS runtime permission.

Three Notification States

  1. Foreground: app is open. FCM does not show a notification — you do.
  2. Background: app is minimized. System tray shows the notification.
  3. Terminated: app is killed. System tray shows it; tap launches the app.

Handling Foreground Messages

FirebaseMessaging.onMessage.listen((RemoteMessage msg) async {
  await flutterLocalNotificationsPlugin.show(
    msg.hashCode,
    msg.notification?.title,
    msg.notification?.body,
    NotificationDetails(
      android: AndroidNotificationDetails(
        'high_importance_channel', 'Important Notifications',
        importance: Importance.high, priority: Priority.high,
      ),
      iOS: const DarwinNotificationDetails(),
    ),
    payload: jsonEncode(msg.data),
  );
});

Handling Background Messages

Background handlers must be top-level functions:

@pragma('vm:entry-point')
Future<void> backgroundHandler(RemoteMessage message) async {
  await Firebase.initializeApp();
  // Persist or process silently
}

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  FirebaseMessaging.onBackgroundMessage(backgroundHandler);
  runApp(MyApp());
}

Notification Taps and Deep Links

// App opened from background
FirebaseMessaging.onMessageOpenedApp.listen((msg) {
  router.go(msg.data['route'] ?? '/');
});

// App launched from terminated state
final initial = await FirebaseMessaging.instance.getInitialMessage();
if (initial != null) {
  router.go(initial.data['route'] ?? '/');
}

Targeting

  • Token: send to a specific device
  • Topic: messaging.subscribeToTopic('news') — fanout to all subscribers
  • Condition: combinations like 'news' in topics && 'sports' in topics
  • User segments: Firebase Analytics audiences

Sending from Your Backend

// Node.js with firebase-admin
const message = {
  token: deviceToken,
  notification: { title: 'New order', body: 'Order #1234 just shipped' },
  data: { route: '/orders/1234' },
  android: { priority: 'high' },
  apns: { payload: { aps: { sound: 'default' } } },
};
await admin.messaging().send(message);

Token Lifecycle

Tokens expire and refresh. Listen for changes and update the server:

FirebaseMessaging.instance.onTokenRefresh.listen(uploadTokenToServer);

Delete tokens on logout to avoid leaking notifications to a different user on the same device.

Rich Notifications

  • iOS: Notification Service Extension to download and attach images
  • Android: BigPictureStyle, action buttons, inline replies
  • Use the data payload to drive routing and analytics

Testing

  • Firebase Console → Cloud Messaging → Test message for one-off sends
  • Real iOS devices only — APNs does not work on simulators
  • Verify all three states: foreground, background, terminated
  • Test on Android with Doze mode enabled

Common Pitfalls

  • Forgetting the iOS APNs auth key upload
  • Sending notification-only payloads when you needed data-only
  • Not setting priority: high on Android, causing delivery delays
  • Deep links that crash on cold start because services are not ready

Conclusion

Push done right is a re-engagement multiplier. FCM with Flutter handles the cross-platform plumbing; the engineering rigor is on you to handle all three app states, deep-link safely, and respect user permission. Build it once and every feature gets a high-leverage channel back to the user.

Tags

FlutterFirebasePush NotificationsFCMMobile App Development

Share this article