Have any questions:

Toll free:9801887718Available 24/7

Email our experts:info@mantraideas.com

In: Mobile App Development

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: ^latest

FlutterFire (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 login

This opens a browser window. Sign in and authorise access.

Step 3: Install FlutterFire CLI

dart pub global activate flutterfire_cli

Step 4: Configure Your Flutter Project

flutterfire configure

You’ll be prompted to:

  1. Select a Firebase project from your account. Choose the project you just created
  2. Select platforms to support (Android, iOS, Web)
  3. 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:

  1. Create APNs Auth Key
  2. Download .p8
  3. 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:

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.

Spread the love

Leave a Reply

Your email address will not be published. Required fields are marked *