firebase_messaging_manager
A production-safe Firebase Messaging lifecycle manager for Flutter that helps you:
- handle foreground, background, and terminated notification flows
- orchestrate
firebase_messagingwithflutter_local_notifications - prevent duplicate notifications using configurable dedupe strategies
- request and check notification permissions on Android and iOS
- receive unified typed notification events through clean callbacks
Features
- Notification lifecycle handling (foreground, background, terminated)
- Notification tap handling from background and terminated launch
- Duplicate notification prevention (
none,messageId,customKey) - Configurable dedupe duration
- Local notification orchestration with Android notification channels
- Unified
NotificationEventmodel for all callbacks - Optional debug logs
Installation
Add the package:
dependencies:
firebase_messaging_manager: ^0.0.1
Then run:
flutter pub get
Firebase Setup
1) Add Firebase to your app
Set up Firebase for Android and iOS in your app using the official Firebase docs:
Recommended CLI flow:
dart pub global activate flutterfire_cli
flutterfire configure
2) Initialize Firebase before using messaging
await Firebase.initializeApp();
Android Setup
- Add notification permission for Android 13+ in
android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
- Ensure internet permission exists:
<uses-permission android:name="android.permission.INTERNET" />
-
Keep a valid launcher icon (
@mipmap/ic_launcher) or pass a custom icon viaandroidNotificationIcon. -
Keep notification channel IDs stable in production (
androidChannelId,androidChannelName).
iOS Setup
- In Xcode (
ios/Runner.xcworkspace) enable capabilities:
- Push Notifications
- Background Modes -> Remote notifications
-
Ensure APNs is configured in Firebase Console (key or certificate).
-
In
ios/Runner/Info.plist, ensure notification usage descriptions are present for your app if needed by your compliance/review flow. -
For iOS foreground delivery behavior, this package sets:
- alert: true
- badge: true
- sound: true
Quick Start
import 'package:firebase_messaging_manager/firebase_messaging_manager.dart';
Future<void> initializePushNotifications() async {
await FirebaseMessagingManager.instance.initialize(
config: const FirebaseMessagingManagerConfig(
requestPermission: true,
showForegroundNotifications: true,
showBackgroundNotifications: false,
duplicateStrategy: DuplicateStrategy.messageId,
dedupeDuration: Duration(seconds: 10),
androidChannelId: 'high_importance_notifications',
androidChannelName: 'High Importance Notifications',
additionalAndroidChannels: <AndroidNotificationChannelConfig>[
AndroidNotificationChannelConfig(
id: 'chat_messages',
name: 'Chat Messages',
description: 'Conversation and message alerts',
),
AndroidNotificationChannelConfig(
id: 'marketing_updates',
name: 'Marketing Updates',
description: 'Promotions and offers',
),
],
enableLogs: true,
),
onForegroundMessage: (event) async {
// Update UI, badges, in-app state, etc.
},
onNotificationTap: (event) async {
// Route user to a screen using event.data
},
onInitialNotification: (event) async {
// Handle terminated -> app launched from notification tap
},
onBackgroundMessage: (event) async {
// Handle data-only background processing if needed
},
);
}
Multiple Android Channels (Categories)
For apps with categories (chat, marketing, orders, alerts), you can register multiple channels and route events to a specific channel:
await FirebaseMessagingManager.instance.initialize(
config: FirebaseMessagingManagerConfig(
androidChannelId: 'general',
androidChannelName: 'General',
additionalAndroidChannels: const [
AndroidNotificationChannelConfig(id: 'chat', name: 'Chat'),
AndroidNotificationChannelConfig(id: 'orders', name: 'Orders'),
AndroidNotificationChannelConfig(id: 'marketing', name: 'Marketing'),
],
resolveAndroidChannelId: (event) {
final type = event.data['type']?.toString();
if (type == 'chat') return 'chat';
if (type == 'order') return 'orders';
if (type == 'marketing') return 'marketing';
return null; // falls back to default channel
},
),
);
What initialize(...) Actually Does
When you call FirebaseMessagingManager.instance.initialize(...), the package:
- optionally requests notification permission (
requestPermission: true) - configures iOS foreground presentation options
- initializes local notification support (
flutter_local_notifications) - creates the configured Android notification channel
- registers FCM listeners for foreground, tap-from-background, and terminated-launch flows
- registers background message handling
- normalizes all incoming events to
NotificationEvent - applies duplicate-prevention checks before invoking callbacks
Local Notification Behavior (Important)
This package is not only callback wiring. It also orchestrates local notifications.
- If
showForegroundNotificationsistrue, foreground messages can trigger a local notification. - If
showBackgroundNotificationsistrue, background data-only messages can trigger a local notification. - Android local notifications use
androidChannelIdandandroidChannelName. - Duplicate checks are applied before callbacks and local-notification display.
In short: callbacks are hooks for your app logic, while display/orchestration can be handled by package config.
Callback Behavior (Who Does What)
onForegroundMessage
- Trigger: FCM message while app is in foreground.
- Package: normalize -> dedupe -> optionally show local notification.
- Your callback: app logic (update UI, refresh data, analytics, etc.).
onNotificationTap
- Trigger: user taps notification and app opens from background.
- Package: normalize -> dedupe.
- Your callback: navigation/routing/business logic.
onInitialNotification
- Trigger: app launched from terminated state via notification tap.
- Package: normalize -> dedupe.
- Your callback: startup routing/deeplink handling.
- Fallback: if not provided, package falls back to
onNotificationTap.
onBackgroundMessage
- Trigger: background message handler path.
- Package: normalize -> dedupe -> optional local notification for data-only payloads (when enabled).
- Your callback: background-safe app work (sync/cache/light processing).
Integration Checklist
- Call
WidgetsFlutterBinding.ensureInitialized()before Firebase init. - Call
Firebase.initializeApp()before lifecycle manager init. - Initialize
FirebaseMessagingManageronce during app startup. - Register callbacks for foreground/tap/initial/background states.
- Verify dedupe strategy against your payload shape (
messageIdor custom key). - Test on real devices for both Android and iOS.
Notification Event Model
All callbacks use NotificationEvent:
class NotificationEvent {
final String? id;
final String? title;
final String? body;
final Map<String, dynamic> data;
final NotificationAppState appState;
final bool wasTapped;
final RemoteMessage rawMessage;
}
Duplicate Prevention
Choose strategy from FirebaseMessagingManagerConfig:
DuplicateStrategy.none: no dedupeDuplicateStrategy.messageId: dedupe by FCM message ID (fallback fingerprint if ID missing)DuplicateStrategy.customKey: dedupe by a payload key (customDuplicateKey)
Example:
const FirebaseMessagingManagerConfig(
duplicateStrategy: DuplicateStrategy.customKey,
customDuplicateKey: 'notification_id',
dedupeDuration: Duration(seconds: 15),
);
Foreground, Background, Terminated Behavior
- Foreground:
onMessageis normalized toNotificationEvent; local notification can be shown. - Background: tap events are delivered through
onNotificationTap. - Terminated: launch payload is delivered through
onInitialNotification.
Permissions
You can let the package request permission during initialization or call helpers manually:
final status = await FirebaseMessagingManager.instance.getPermissionStatus();
final settings = await FirebaseMessagingManager.instance.requestPermission();
Common Issues
- No notifications on iOS: verify APNs key/cert and iOS capabilities.
- No foreground banners: ensure
showForegroundNotificationsistrue. - Duplicate messages: choose a stricter dedupe strategy and verify payload identifiers.
- Tap callbacks not firing: ensure
initialize()is called once early in app startup. - Data-only background flow not observed: verify payload is truly data-only and app/background constraints are satisfied.
FAQ
Does this replace Firebase Messaging?
No. It wraps firebase_messaging and flutter_local_notifications into a cleaner lifecycle API.
Can I still access raw RemoteMessage?
Yes, each NotificationEvent includes rawMessage.
Is this package opinionated about routing?
No, routing decisions stay in your callback handlers.
Roadmap
- Additional sample scenarios in
example/(routing and state restoration) - More advanced Android/iOS platform customization
- Expanded testing for lifecycle edge cases