Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Use when setting up Firebase Cloud Messaging, managing permissions and tokens, handling background/foreground notification taps, or dispatching messages server-side (HTTP v1).
.claude/skills/evanca-firebase-messaging/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 99% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 115% | 0% |
| case-16 | ✗→✓ | ▲ Improved | 104% | 0% |
| case-11 | ✓→✗ | ▼ Worse | 122% | 0% |
| case-12 | ✓→✓ | = Same ✓ | 165% | 0% |
This skill defines how to correctly use Firebase Cloud Messaging (FCM) in Flutter applications.
Use this skill when:
flutter pub add firebase_messagingiOS:
Android:
onCreate() and onResume().Web:
firebase-messaging-sw.js in your web/ directory:jsimportScripts("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:
dartFirebaseMessaging.onMessage.listen((RemoteMessage message) { print('Foreground message data: ${message.data}'); if (message.notification != null) { print('Notification: ${message.notification}'); } });
Background messages:
dart@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.dartNotificationSettings 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):
dartfinal fcmToken = await FirebaseMessaging.instance.getToken();
Web — provide VAPID key:
dartfinal fcmToken = await FirebaseMessaging.instance.getToken( vapidKey: "BKagOny0KF_2pCJQ3m....moL0ewzQ8rZu" );
Listen for token refresh:
dartFirebaseMessaging.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:
dartfinal 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.
dart 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: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 = NODisable auto-init — Android (AndroidManifest.xml):
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:
dartawait 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:
dartRemoteMessage? initialMessage = await FirebaseMessaging.instance.getInitialMessage(); if (initialMessage != null) { // Navigate based on message content }
App was in background:
dartFirebaseMessaging.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.
dart// Subscribe await FirebaseMessaging.instance.subscribeToTopic("weather_alerts"); // Unsubscribe await FirebaseMessaging.instance.unsubscribeFromTopic("weather_alerts");
> Note: subscribeToTopic() and unsubscribeFromTopic() 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:
javascriptconst { 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(); }
Other measured skills in the registry, with their headline benchmark lift.