firebase_messaging_handler
Ship reliable Flutter notifications, inboxes, badges, and click handling.
Qoder
11 sectionsFlutter Package
Overview
firebase_messaging_handler turns Firebase Cloud Messaging into an application-level messaging system. It normalizes notification clicks across lifecycles, supports inbox and in-app messaging patterns, adds diagnostics, and gives teams a clearer path from raw FCM payloads to product behavior.
- Unified notification click streams across foreground, background, and terminated app states
- Cross-platform support for Android, iOS, Web, and desktop local-mode flows
- Automatic token management with single backend callback
- Smart channel fallback for Android notifications
- Interactive notification actions with custom payloads
- One-time and recurring notification scheduling
- Cross-platform badge count management
- Notification grouping (Android) and conversation threads (iOS)
- Custom sound support per platform
- Analytics hooks for delivery, impressions, and actions
- Comprehensive notification diagnostics (Notification Doctor)
- Quiet hours and frequency caps for in-app messaging
- Data-only payload bridging to local notifications
- Typed inbox storage with read/delete operations
- Rich in-app templates — dialog, full-screen, bottom sheet, banner, snackbar
- Foreground notification customization with smart fallbacks
- Testing utilities and mock data generation
Installation
Add the package to pubspec.yaml. It wraps Firebase Messaging behavior behind a higher-level API, so teams can centralize notification setup instead of scattering lifecycle code through the app.
dependencies:
firebase_messaging_handler: ^1.0.0Android Setup
Add the required permissions to AndroidManifest.xml based on the features you need.
<!-- Basic notifications -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- Scheduled notifications (add these too) -->
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />Quick Start
Initialize once at app startup, wire the backend token callback, and subscribe to a single click stream that works across app lifecycle states.
final Stream<NotificationData?>? clickStream =
await FirebaseMessagingHandler.instance.init(
senderId: 'your_sender_id',
androidChannelList: [
NotificationChannelData(
id: 'default_channel',
name: 'Default Notifications',
importance: NotificationImportanceEnum.high,
priority: NotificationPriorityEnum.high,
playSound: true,
enableVibration: true,
),
],
androidNotificationIconPath: '@drawable/ic_notification',
updateTokenCallback: (fcmToken) async {
// Send token to your backend
return true;
},
);
clickStream?.listen((NotificationData? data) {
if (data != null) {
print('Notification clicked: ${data.title}');
}
});Unified Handler
Use one callback to inspect normalized payloads across foreground, background, and terminated states before deciding whether the package or your app should render the notification.
await FirebaseMessagingHandler.instance.setUnifiedMessageHandler(
(NormalizedMessage message, NotificationLifecycle lifecycle) async {
debugPrint('[unified] lifecycle=$lifecycle title=${message.title}');
return false; // false = let plugin render the notification
},
);In-App Messaging
Deliver rich in-app experiences from silent FCM payloads using reusable templates. Register templates, then the plugin automatically triggers them when a matching payload arrives.
FirebaseMessagingHandler.instance.registerInAppNotificationTemplates({
'builtin_generic': BuiltInInAppTemplates.generic(
onAction: (actionId, data) {
debugPrint('Action: $actionId');
},
),
});- Trigger types: immediate, next_foreground, app_launch, custom
- Built-in layouts: dialog, full_screen, bottom_sheet, banner, tooltip, carousel, snackbar
- Quiet hours and per-template frequency caps
- Deferred payloads are re-queued automatically
Notification Inbox
Typed, persistent inbox backed by SharedPreferences. Supports paged fetch, upsert, markRead, delete, and a built-in NotificationInboxView widget.
final inbox = InboxStorageService();
await inbox.upsert(NotificationInboxItem(
id: 'item1',
title: 'Message',
timestamp: DateTime.now(),
));
final items = await inbox.fetch(page: 0, pageSize: 20);Foreground Notification Customization
Own the fallback notification UI that appears while your app is active. Return null to use plugin defaults, or set enabled: false to suppress entirely.
FirebaseMessagingHandler.instance.setForegroundNotificationOptions(
ForegroundNotificationOptions(
androidBuilder: (context) => AndroidNotificationDetails(
'promo_channel',
'Promotions',
styleInformation: BigPictureStyleInformation(
DrawableResourceAndroidBitmap('large_image'),
),
),
),
);Notification Diagnostics
Run the built-in notification doctor to inspect permissions, token state, badge support, web readiness, pending scheduled items, invalid payloads, and background wiring in one call.
final diagnostics =
await FirebaseMessagingHandler.instance.runDiagnostics();
debugPrint('Diagnostics: ${diagnostics.toMap()}');
for (final rec in diagnostics.recommendations) {
debugPrint('Recommendation: $rec');
}- Checks: permissionsGranted, fcmTokenAvailable, badgeSupported, backgroundHandlerRegistered
- Web diagnostics: notification API, secure-context, service-worker
- pendingNotificationCount for scheduled items
- invalidPayloadCount for malformed data-only payloads
- recommendations list with actionable fixes
Troubleshooting
Common issues and solutions.
- No notifications: verify Firebase config files, sender ID, and AndroidManifest permissions
- Scheduling fails: Android 13+ requires runtime permission request for SCHEDULE_EXACT_ALARM
- iOS APNs error: configure APNs key in Apple Developer Console and upload .p8 to Firebase Console
- Custom sounds silent: ensure files are in correct platform directories with proper formats
- Analytics not tracking: ensure callback is set before init()
- Debug mode: plugin logs detailed information automatically in debug builds
Testing Utilities
Mock data generation and test mode for testing without Firebase.
final mockMessage = FirebaseMessagingHandler.createMockRemoteMessage(
title: 'Test',
data: {'key': 'value'},
);
FirebaseMessagingHandler.setTestMode(true);
FirebaseMessagingHandler.addMockNotification(mockMessage);