Setting up Firebase notifications is actually pretty simple, but it can get confusing and a bit frustrating if you miss a step or don’t follow the official docs closely. I’ve run into those issues myself, so in this guide I’ll walk you through a clean and simple setup to help you avoid the usual headaches and get it working smoothly.
This tutorial covers:
- FlutterFire setup
- Android setup
- iOS setup
- foreground notifications
- background notifications
- terminated state handling
- local notifications
- FCM token setup
- notification click handling
- troubleshooting
We’ll use:
firebase_messaging
flutter_local_notifications
flutterfire_cli
Packages
Add dependencies:
dependencies:
flutter:
sdk: flutter
firebase_core: ^latest
firebase_messaging: ^latest
flutter_local_notifications: ^latestFlutterFire (Quick and easy approach)
Red light, red light Hold your horses! What is this and why use it?
Think of FlutterFire CLI as your Firebase setup assistant.
Instead of manually configuring files for Android and iOS this automates most of the Firebase setup process. It generates the required configuration (firebase_options.dart) and connects your project to Firebase in a consistent and error free way. This reduces setup mistakes, saves time, and ensures your project follows the latest official Firebase integration standards.
So, lets quickly get started
Step 1: Create a Firebase Project if not created.
- Go to console.firebase.google.com
- Click Add Project
- Enter your project name (e.g. MyNotificationApp)
- Enable or disable Google Analytics as needed
Click Create Project
Step 2: Login to Firebase
Make sure you’re logged into the same Google account where your Firebase project was created:
firebase loginThis opens a browser window. Sign in and authorise access.
Step 3: Install FlutterFire CLI
dart pub global activate flutterfire_cliStep 4: Configure Your Flutter Project
flutterfire configureYou’ll be prompted to:
- Select a Firebase project from your account. Choose the project you just created
- Select platforms to support (Android, iOS, Web)
- FlutterFire will automatically generate firebase_options.dart and connect your app to Firebase across all selected platformsThis will automatically generate firebase_options.dart and connect your app to Firebase across all platforms.
Configure Firebase:
Official docs:
Final Project Structure
lib/
│
├── main.dart
│
├── services/
│ ├── firebase_notification_service.dart
│ └── notification_service.dart
firebase_notification_service: Acts as the bridge between Firebase and your Flutter app.
notification_service: Controls how notifications are shown to the user on the device
Android Setup
1. Notification Permission (Android 13+)
Open:
android/app/src/main/AndroidManifest.xml
Add:
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>2. Add Notification Channel
Inside
AndroidManifest.xml
<application>
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="high_importance_channel"/>
</application>iOS Setup
Open:
ios/Runner.xcworkspace
Enable iOS Capabilities
Inside Xcode:
Runner
→ Signing & Capabilities
Enable:
Push Notifications
AND
Background Modes
Check:
- Remote notifications
- Background fetch
Configure APNs
Inside Apple Developer account:
- Create APNs Auth Key
- Download .p8
- Upload it to Firebase Console
Without APNs:
iOS notifications will not work.
AppDelegate.swift
Open:
ios/Runner/AppDelegate.swift
Use this setup:
import UIKit
import Flutter
import UserNotifications
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
UNUserNotificationCenter.current().delegate = self
application.registerForRemoteNotifications()
GeneratedPluginRegistrant.register(with: self)
return super.application(
application,
didFinishLaunchingWithOptions: launchOptions
)
}
}Why We Don’t Use FirebaseApp.configure()
When using:
flutterfire configure
FlutterFire generates:
firebase_options.dart
Add Firebase initializes on main.dart after “WidgetsFlutterBinding.ensureInitialized()”
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);So:
FirebaseApp.configure() is NOT needed in Flutter apps.
If both are used, you may get:
[core/duplicate-app] A Firebase App named “[DEFAULT]” already exists.
Note: You can also handle iOS notifications manually by configuring AppDelegate.swift, but for simplicity and cleaner setup, we’ll stick with this approach instead.
Notification Service
Create:
lib/services/notification_service.dart
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
class NotificationService {
NotificationService._();
static final NotificationService instance =
NotificationService._();
final FlutterLocalNotificationsPlugin
flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
Future<void> initialize() async {
const android =
AndroidInitializationSettings(
'@mipmap/ic_launcher',
);
const ios = DarwinInitializationSettings();
const settings = InitializationSettings(
android: android,
iOS: ios,
);
await flutterLocalNotificationsPlugin.initialize(
settings,
);
await _createNotificationChannel();
}
Future<void> _createNotificationChannel() async {
const AndroidNotificationChannel channel =
AndroidNotificationChannel(
'high_importance_channel',
'High Importance Notifications',
description:
'Used for important notifications.',
importance: Importance.max,
);
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(channel);
}
Future<void> showNotification({
required int id,
required String title,
required String body,
}) async {
const androidDetails =
AndroidNotificationDetails(
'high_importance_channel',
'High Importance Notifications',
importance: Importance.max,
priority: Priority.high,
);
const iosDetails =
DarwinNotificationDetails();
const details = NotificationDetails(
android: androidDetails,
iOS: iosDetails,
);
await flutterLocalNotificationsPlugin.show(
id,
title,
body,
details,
);
}
}Firebase Notification Service
Create:
lib/services/firebase_notification_service.dart
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart';
import 'notification_service.dart';
class FirebaseNotificationService {
FirebaseNotificationService._();
static final FirebaseNotificationService instance =
FirebaseNotificationService._();
final FirebaseMessaging _messaging =
FirebaseMessaging.instance;
Future<void> initialize() async {
await _requestPermission();
await _setupHandlers();
await _getFCMToken();
}
Future<void> _requestPermission() async {
final settings =
await _messaging.requestPermission(
alert: true,
badge: true,
sound: true,
);
debugPrint(
'Permission: ${settings.authorizationStatus}',
);
}
Future<void> _getFCMToken() async {
final token = await _messaging.getToken();
debugPrint('FCM Token: $token');
_messaging.onTokenRefresh.listen(
(newToken) {
debugPrint(
'Refreshed Token: $newToken',
);
},
);
}
Future<void> _setupHandlers() async {
/// Foreground Notifications
FirebaseMessaging.onMessage.listen(
(RemoteMessage message) {
debugPrint(
'Foreground Notification',
);
if (message.notification != null) {
NotificationService.instance
.showNotification(
id: message.hashCode,
title:
message.notification?.title ?? '',
body:
message.notification?.body ?? '',
);
}
},
);
/// Notification Clicked From Background
FirebaseMessaging.onMessageOpenedApp.listen(
(RemoteMessage message) {
debugPrint(
'Notification Clicked From Background',
);
},
);
/// Notification Clicked From Terminated State
final initialMessage =
await _messaging.getInitialMessage();
if (initialMessage != null) {
debugPrint(
'Notification Clicked From Terminated State',
);
}
}
}main.dart
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'firebase_options.dart';
import 'services/firebase_notification_service.dart';
import 'services/notification_service.dart';
@pragma('vm:entry-point')// Don't forget to implement this!
Future<void> firebaseMessagingBackgroundHandler(
RemoteMessage message,
) async {
await Firebase.initializeApp(
options:
DefaultFirebaseOptions.currentPlatform,
);
debugPrint(
'Background Message: ${message.messageId}',
);
}
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options:
DefaultFirebaseOptions.currentPlatform,
);
FirebaseMessaging.onBackgroundMessage(
firebaseMessagingBackgroundHandler,
);
await NotificationService.instance
.initialize();
await FirebaseNotificationService.instance
.initialize();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: const Text(
'Flutter FCM Notifications',
),
),
body: const Center(
child: Text(
'Firebase Notification Setup Complete',
),
),
),
);
}
}Handle Foreground Notifications
By default:
flutter push notification not showing foreground android.
This is expected behavior.
FCM does NOT display notifications automatically while the app is open.
That’s why we use:
flutter_local_notifications
inside:
FirebaseMessaging.onMessage.listen()
This solves:
- flutter local notifications foreground
- handle FCM notifications in flutter foreground
Handle Background Notifications
Register background handler:
FirebaseMessaging.onBackgroundMessage(
firebaseMessagingBackgroundHandler,
);Requirements:
- handler must be top-level
- use
@pragma('vm:entry-point') - initialize Firebase inside handler
This is required for:
- flutter FCM background notification handler
Handle Terminated State Notifications
Use:
FirebaseMessaging.instance.getInitialMessage()
This handles:
- firebase getInitialMessage flutter terminated state
Without this, notification clicks from the app may not work correctly.
Send Push Notification Flutter Firebase
Go to:
Firebase Console
→ Messaging
→ Create Notification
Paste FCM token and send.
This is the easiest way to:
- test firebase push notifications in flutter app
- send push notifications from firebase console to flutter
Flutter FCM Notifications Not Working Troubleshooting
iOS notifications not working
Usually caused by:
- APNs missing
- capabilities disabled
- testing on simulator
Note: “Firebase Cloud Messaging (FCM) does not support real push notifications on the iOS Simulator because APNs device registration is not available. You must test push notifications on a physical iOS device.”
Foreground notifications not showing
Use:
flutter_local_notifications
FCM does not automatically show foreground notifications.
Background handler not triggered
Make sure:
@pragma('vm:entry-point')Eg:
@pragma('vm:entry-point')
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
debugPrint("📩 Background message received: ${message.messageId}");
}exists above the background handler.
Android notifications missing
Verify:
POST_NOTIFICATIONS
permission exists.
Notification click not opening screen
Handle both:
FirebaseMessaging.onMessageOpenedApp
AND
FirebaseMessaging.instance.getInitialMessage()
Why Flutter Push Notifications Fail
Most common reasons:
- APNs not configured
- foreground notifications not handled
- Android notification channel missing
- notification permissions denied
- testing on iOS simulator
- token expired
- background handler missing
Final Thoughts
This implementation gives you a clean and production-ready architecture for:
- flutter firebase push notifications
- firebase cloud messaging flutter
- flutter firebase messaging
- flutter FCM push notification
- flutter firebase push notification example
- flutter firebase messaging handle notifications
- flutter firebase push notification token setup
- flutter FCM background notification handler
- firebase cloud messaging flutter tutorial
- firebase_messaging flutter
Recommended references:
- https://firebase.flutter.dev/docs/messaging/usage
- https://firebase.google.com/docs/cloud-messaging/flutter/receive
- https://firebase.google.com/docs/flutter/setup
Conclusion
And that’s it. Your Firebase setup is now complete. Using FlutterFire makes the entire integration process much cleaner compared to manually configuring everything for Android and iOS.
If you run into any issues or have better insights to share, feel free to reach out. Always happy to help fellow developers.