This skill defines how to correctly use Firebase Cloud Messaging (FCM) in Flutter applications.
Use this skill when:
flutter pub add firebase_messaging
iOS:
Android:
onCreate() and onResume().Web:
firebase-messaging-sw.js in your web/ directory:importScripts("https://www.gstatic.com/firebasejs/10.7.0/firebase-app-compat.js");
importScripts("https://www.gstatic.com/firebasejs/10.7.0/firebase-messaging-compat.js");
firebase.initializeApp({ /* your config */ });
const messaging = firebase.messaging();
messaging.onBackgroundMessage((message) => {
console.log("onBackgroundMessage", message);
});
Foreground messages:
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
print('Foreground message data: ${message.data}');
if (message.notification != null) {
print('Notification: ${message.notification}');
}
});
Background messages:
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
// Initialize Firebase before using other Firebase services in background
await Firebase.initializeApp();
print("Background message: ${message.messageId}");
}
void main() {
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
runApp(MyApp());
}
Background handler rules:
@pragma('vm:entry-point') (Flutter 3.3.0+) to prevent removal during tree shaking in release mode.Firebase.initializeApp() before using any other Firebase services.NotificationSettings settings = await FirebaseMessaging.instance.requestPermission(
alert: true,
badge: true,
sound: true,
announcement: false,
carPlay: false,
criticalAlert: false,
provisional: false,
);
print('Authorization status: ${settings.authorizationStatus}');
authorizationStatus returns authorized if the user has not disabled notifications in OS settings.provisional: true) to let users choose notification types after receiving their first notification.Get FCM registration token (use to send messages to a specific device):
final fcmToken = await FirebaseMessaging.instance.getToken();
Web — provide VAPID key:
final fcmToken = await FirebaseMessaging.instance.getToken(
vapidKey: "BKagOny0KF_2pCJQ3m....moL0ewzQ8rZu"
);
Listen for token refresh:
FirebaseMessaging.instance.onTokenRefresh.listen((fcmToken) {
// Send updated token to your application server
}).onError((err) {
// Handle error
});
Apple platforms — ensure APNS token is available before FCM calls:
final apnsToken = await FirebaseMessaging.instance.getAPNSToken();
if (apnsToken != null) {
// Safe to make FCM plugin API requests
}
Token Lifecycle (Auth State): Tokens should be tied to user sessions. Save the token to your database when a user signs in, and delete the token (or remove it from the user's document) when they sign out. An FCM token is device-specific, not inherently tied to user auth data — failing to clear it on sign-out means the next user on that device might receive the previous user's notifications.
await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
alert: true,
badge: true,
sound: true,
);
onMessage stream and manually display a visual cue (using your own UI logic or a local notifications plugin).meta-data to your <application> block in AndroidManifest.xml:
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="high_importance_channel" />
Disable auto-init — iOS (Info.plist):
FirebaseMessagingAutoInitEnabled = NO
Disable auto-init — Android (AndroidManifest.xml):
<meta-data android:name="firebase_messaging_auto_init_enabled" android:value="false" />
<meta-data android:name="firebase_analytics_collection_enabled" android:value="false" />
Re-enable at runtime:
await FirebaseMessaging.instance.setAutoInitEnabled(true);
Important: The iOS simulator does not display images in push notifications. Test on a physical device.
Messaging.serviceExtension().populateNotificationContent() in the extension for image handling.FirebaseMessaging Swift package to your extension target.Firebase/Messaging pod to your Podfile.When a user taps a notification, the app opens (or is brought to the foreground). Handle the interaction in both cases:
App was terminated:
RemoteMessage? initialMessage =
await FirebaseMessaging.instance.getInitialMessage();
if (initialMessage != null) {
// Navigate based on message content
}
App was in background:
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
// Navigate based on message content
});
Always handle both scenarios to ensure a smooth user experience regardless of app state when the notification was received.
// Subscribe
await FirebaseMessaging.instance.subscribeToTopic("weather_alerts");
// Unsubscribe
await FirebaseMessaging.instance.unsubscribeFromTopic("weather_alerts");
Note:
subscribeToTopic()andunsubscribeFromTopic()are not supported for web clients via the Flutter plugin.
The official Firebase documentation often obscures the exact steps for sending a test push notification. To fire a push (test or real) using the Firebase Console:
+ icon to add it.To send real automated push notifications to production users, you must use a server implementation (via the FCM HTTP v1 API or the Firebase Admin SDK) rather than the console.
The legacy FCM server key endpoint was deprecated in June 2024 — HTTP v1 is the only supported option for sending pushes.
To authenticate server-to-server calls for HTTP v1, you need a Service Account:
.json file).FIREBASE_SERVICE_ACCOUNT) to your backend.To send an FCM HTTP v1 message, your backend must:
https://www.googleapis.com/auth/firebase.messaging, endpoint https://oauth2.googleapis.com/token).POST a JSON payload to https://fcm.googleapis.com/v1/projects/{project_id}/messages:send.Here is a minimal, complete working example using Node.js and the google-auth-library:
const { GoogleAuth } = require('google-auth-library');
// Read the securely-stored service account JSON from environment
const credentials = JSON.parse(process.env.FIREBASE_SERVICE_ACCOUNT);
async function getAccessToken() {
const auth = new GoogleAuth({
credentials,
scopes: ['https://www.googleapis.com/auth/firebase.messaging']
});
const client = await auth.getClient();
const token = await client.getAccessToken();
return token.token;
}
async function sendPushNotification(fcmToken, title, body) {
const accessToken = await getAccessToken();
const projectId = credentials.project_id;
const url = `https://fcm.googleapis.com/v1/projects/${projectId}/messages:send`;
const payload = {
message: {
token: fcmToken,
notification: {
title: title,
body: body,
},
// Target specific platform features (e.g., channel on Android, sound on iOS)
android: {
notification: {
channel_id: 'high_importance_channel',
}
},
apns: {
payload: {
aps: {
sound: 'default',
}
}
}
}
};
const response = await fetch(url, {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
return response.json();
}