{"slug":"firebase-messaging","title":"firebase-messaging","summary":"Use when setting up Firebase Cloud Messaging, managing permissions and tokens, handling background/foreground notification taps, or dispatching messages server-side (HTTP v1).","platform":"Claude","tags":[],"authorName":"LLM Mart","authorSlug":"llm-mart","score":0,"source":"github","price":null,"verified":false,"createdAt":"2026-08-30T09:57:18.333533Z","repo":{"url":"https://github.com/evanca/flutter-ai-rules","stars":642,"forks":66,"license":"MIT","updatedAt":"2026-09-14T07:51:38Z"},"bodyHtml":"<hr>\n<h2>name: firebase-messaging\ndescription: \"Use when setting up Firebase Cloud Messaging, managing permissions and tokens, handling background/foreground notification taps, or dispatching messages server-side (HTTP v1).\"\nlicense: MIT</h2>\n<h1>Firebase Cloud Messaging Skill</h1>\n<p>This skill defines how to correctly use Firebase Cloud Messaging (FCM) in Flutter applications.</p>\n<h2>When to Use</h2>\n<p>Use this skill when:</p>\n<ul>\n<li>Setting up push notifications with FCM in a Flutter project.</li>\n<li>Handling messages in foreground, background, and terminated states.</li>\n<li>Managing notification permissions and FCM tokens.</li>\n<li>Configuring platform-specific notification display behavior.</li>\n</ul>\n<hr>\n<h2>1. Setup and Configuration</h2>\n<pre><code>flutter pub add firebase_messaging\n</code></pre>\n<p><strong>iOS:</strong></p>\n<ul>\n<li>Enable <strong>Push Notifications</strong> and <strong>Background Modes</strong> in Xcode.</li>\n<li>Upload your <strong>APNs authentication key</strong> to Firebase before using FCM.</li>\n<li>Do <strong>not</strong> disable method swizzling — it is required for FCM token handling.</li>\n<li>Ensure the bundle ID for your APNs authentication key matches your app's bundle ID.</li>\n</ul>\n<p><strong>Android:</strong></p>\n<ul>\n<li>Devices must run <strong>Android 4.4+</strong> with Google Play services installed.</li>\n<li>Check for Google Play services compatibility in both <code>onCreate()</code> and <code>onResume()</code>.</li>\n</ul>\n<p><strong>Web:</strong></p>\n<ul>\n<li>Create and register a service worker file named <code>firebase-messaging-sw.js</code> in your <code>web/</code> directory:</li>\n</ul>\n<pre><code>importScripts(\"https://www.gstatic.com/firebasejs/10.7.0/firebase-app-compat.js\");\nimportScripts(\"https://www.gstatic.com/firebasejs/10.7.0/firebase-messaging-compat.js\");\n\nfirebase.initializeApp({ /* your config */ });\n\nconst messaging = firebase.messaging();\n\nmessaging.onBackgroundMessage((message) =&gt; {\n  console.log(\"onBackgroundMessage\", message);\n});\n</code></pre>\n<hr>\n<h2>2. Message Handling</h2>\n<p><strong>Foreground messages:</strong></p>\n<pre><code>FirebaseMessaging.onMessage.listen((RemoteMessage message) {\n  print('Foreground message data: ${message.data}');\n  if (message.notification != null) {\n    print('Notification: ${message.notification}');\n  }\n});\n</code></pre>\n<p><strong>Background messages:</strong></p>\n<pre><code>@pragma('vm:entry-point')\nFuture&lt;void&gt; _firebaseMessagingBackgroundHandler(RemoteMessage message) async {\n  // Initialize Firebase before using other Firebase services in background\n  await Firebase.initializeApp();\n  print(\"Background message: ${message.messageId}\");\n}\n\nvoid main() {\n  FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);\n  runApp(MyApp());\n}\n</code></pre>\n<p>Background handler rules:</p>\n<ul>\n<li>Must be a <strong>top-level function</strong> (not anonymous, not a class method).</li>\n<li>Annotate with <code>@pragma('vm:entry-point')</code> (Flutter 3.3.0+) to prevent removal during tree shaking in release mode.</li>\n<li>Cannot update app state or execute UI-impacting logic — runs in a separate isolate.</li>\n<li>Call <code>Firebase.initializeApp()</code> before using any other Firebase services.</li>\n</ul>\n<hr>\n<h2>3. Permissions</h2>\n<pre><code>NotificationSettings settings = await FirebaseMessaging.instance.requestPermission(\n  alert: true,\n  badge: true,\n  sound: true,\n  announcement: false,\n  carPlay: false,\n  criticalAlert: false,\n  provisional: false,\n);\n\nprint('Authorization status: ${settings.authorizationStatus}');\n</code></pre>\n<ul>\n<li><strong>iOS / macOS / Web / Android 13+:</strong> Must request permission before receiving FCM payloads.</li>\n<li><strong>Android &lt; 13:</strong> <code>authorizationStatus</code> returns <code>authorized</code> if the user has not disabled notifications in OS settings.</li>\n<li><strong>Android 13+:</strong> Track permission requests in your app — there's no way to determine if the user chose to grant/deny.</li>\n<li>Use <strong>provisional permissions</strong> on iOS (<code>provisional: true</code>) to let users choose notification types after receiving their first notification.</li>\n</ul>\n<hr>\n<h2>4. Token Management</h2>\n<p><strong>Get FCM registration token (use to send messages to a specific device):</strong></p>\n<pre><code>final fcmToken = await FirebaseMessaging.instance.getToken();\n</code></pre>\n<p><strong>Web — provide VAPID key:</strong></p>\n<pre><code>final fcmToken = await FirebaseMessaging.instance.getToken(\n  vapidKey: \"BKagOny0KF_2pCJQ3m....moL0ewzQ8rZu\"\n);\n</code></pre>\n<p><strong>Listen for token refresh:</strong></p>\n<pre><code>FirebaseMessaging.instance.onTokenRefresh.listen((fcmToken) {\n  // Send updated token to your application server\n}).onError((err) {\n  // Handle error\n});\n</code></pre>\n<p><strong>Apple platforms — ensure APNS token is available before FCM calls:</strong></p>\n<pre><code>final apnsToken = await FirebaseMessaging.instance.getAPNSToken();\nif (apnsToken != null) {\n  // Safe to make FCM plugin API requests\n}\n</code></pre>\n<p><strong>Token Lifecycle (Auth State):</strong>\nTokens should be tied to user sessions. Save the token to your database when a user signs in, and <strong>delete</strong> 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.</p>\n<hr>\n<h2>5. Platform-Specific Behavior</h2>\n<ul>\n<li><strong>iOS:</strong> If the user swipes away the app from the app switcher, it must be <strong>manually reopened</strong> for background messages to work again.</li>\n<li><strong>Android:</strong> If the user force-quits from device settings, the app must be <strong>manually reopened</strong>.</li>\n<li><strong>iOS foreground notifications:</strong> Update presentation options to display notifications while the app is in the foreground:\n<pre><code>await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(\n  alert: true,\n  badge: true,\n  sound: true,\n);\n</code></pre>\n</li>\n<li><strong>Android foreground notifications:</strong> Notification messages arriving while the app is in the foreground won't display a visible notification by default. You must consume the payload via the <code>onMessage</code> stream and manually display a visual cue (using your own UI logic or a local notifications plugin).</li>\n<li><strong>Android default channel:</strong> To set a default channel for background notifications, add this <code>meta-data</code> to your <code>&lt;application&gt;</code> block in <code>AndroidManifest.xml</code>:\n<pre><code>&lt;meta-data\n    android:name=\"com.google.firebase.messaging.default_notification_channel_id\"\n    android:value=\"high_importance_channel\" /&gt;\n</code></pre>\n</li>\n</ul>\n<hr>\n<h2>6. Auto-Initialization Control</h2>\n<p><strong>Disable auto-init — iOS</strong> (<code>Info.plist</code>):</p>\n<pre><code>FirebaseMessagingAutoInitEnabled = NO\n</code></pre>\n<p><strong>Disable auto-init — Android</strong> (<code>AndroidManifest.xml</code>):</p>\n<pre><code>&lt;meta-data android:name=\"firebase_messaging_auto_init_enabled\" android:value=\"false\" /&gt;\n&lt;meta-data android:name=\"firebase_analytics_collection_enabled\" android:value=\"false\" /&gt;\n</code></pre>\n<p><strong>Re-enable at runtime:</strong></p>\n<pre><code>await FirebaseMessaging.instance.setAutoInitEnabled(true);\n</code></pre>\n<ul>\n<li>The auto-init setting <strong>persists across app restarts</strong> once set.</li>\n</ul>\n<hr>\n<h2>7. iOS Image Notifications</h2>\n<blockquote>\n<p><strong>Important:</strong> The iOS simulator does <strong>not</strong> display images in push notifications. Test on a physical device.</p>\n</blockquote>\n<ul>\n<li>Add a <strong>Notification Service Extension</strong> in Xcode.</li>\n<li>Use <code>Messaging.serviceExtension().populateNotificationContent()</code> in the extension for image handling.</li>\n<li>Swift: add the <code>FirebaseMessaging</code> Swift package to your extension target.</li>\n<li>Objective-C: add the <code>Firebase/Messaging</code> pod to your Podfile.</li>\n</ul>\n<hr>\n<h2>8. Notification Interaction Handling</h2>\n<p>When a user taps a notification, the app opens (or is brought to the foreground). Handle the interaction in both cases:</p>\n<p><strong>App was terminated:</strong></p>\n<pre><code>RemoteMessage? initialMessage =\n    await FirebaseMessaging.instance.getInitialMessage();\nif (initialMessage != null) {\n  // Navigate based on message content\n}\n</code></pre>\n<p><strong>App was in background:</strong></p>\n<pre><code>FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {\n  // Navigate based on message content\n});\n</code></pre>\n<p>Always handle <strong>both</strong> scenarios to ensure a smooth user experience regardless of app state when the notification was received.</p>\n<hr>\n<h2>9. Topic Messaging</h2>\n<ul>\n<li>Subscribing to a topic allows sending messages to multiple devices that have opted in.</li>\n<li>Topic messages are best suited for publicly available information (e.g., weather updates), optimized for throughput rather than latency.</li>\n</ul>\n<pre><code>// Subscribe\nawait FirebaseMessaging.instance.subscribeToTopic(\"weather_alerts\");\n\n// Unsubscribe\nawait FirebaseMessaging.instance.unsubscribeFromTopic(\"weather_alerts\");\n</code></pre>\n<blockquote>\n<p><strong>Note:</strong> <code>subscribeToTopic()</code> and <code>unsubscribeFromTopic()</code> are not supported for web clients via the Flutter plugin.</p>\n</blockquote>\n<hr>\n<h2>10. Sending a Test Message</h2>\n<p>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:</p>\n<ol>\n<li>Obtain your device's <strong>FCM registration token</strong> (see Section 4).</li>\n<li>Go to the <a href=\"https://console.firebase.google.com/\">Firebase Console</a> and select your project.</li>\n<li>In the left navigation panel, find the <strong>Engage</strong> (or <strong>Run</strong>) section and click <strong>Messaging</strong> (or <strong>Cloud Messaging</strong>).</li>\n<li>Click <strong>New campaign</strong> and select <strong>Notifications</strong>.</li>\n<li>Enter a <strong>Notification title</strong> and <strong>Notification text</strong>.</li>\n<li>Click <strong>Send test message</strong> (often a button on the right side of the screen).</li>\n<li>In the dialog, enter your <strong>FCM registration token</strong> and click the <code>+</code> icon to add it.</li>\n<li>Make sure the token is checked, then click <strong>Test</strong>.</li>\n</ol>\n<blockquote>\n<p>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.</p>\n</blockquote>\n<hr>\n<h2>11. Server-Side Credentials &amp; Security</h2>\n<p>The legacy FCM server key endpoint was deprecated in June 2024 — <strong>HTTP v1 is the only supported option</strong> for sending pushes.</p>\n<p>To authenticate server-to-server calls for HTTP v1, you need a Service Account:</p>\n<ol>\n<li>Go to <strong>Firebase Console → Project settings → Service accounts</strong>.</li>\n<li>Click <strong>Generate new private key</strong> (downloads a <code>.json</code> file).</li>\n<li><strong>CRITICAL:</strong> This file contains highly sensitive secrets and <strong>must never be committed to git</strong>.</li>\n<li>Store the file securely (e.g., in a Secret Manager) or pass its stringified contents as an environment variable (like <code>FIREBASE_SERVICE_ACCOUNT</code>) to your backend.</li>\n</ol>\n<hr>\n<h2>12. Sending Messages (HTTP v1)</h2>\n<p>To send an FCM HTTP v1 message, your backend must:</p>\n<ol>\n<li>Complete an OAuth2 JWT exchange (sign with RS256, scope <code>https://www.googleapis.com/auth/firebase.messaging</code>, endpoint <code>https://oauth2.googleapis.com/token</code>).</li>\n<li>Construct and <code>POST</code> a JSON payload to <code>https://fcm.googleapis.com/v1/projects/{project_id}/messages:send</code>.</li>\n</ol>\n<p>Here is a minimal, complete working example using Node.js and the <code>google-auth-library</code>:</p>\n<pre><code>const { GoogleAuth } = require('google-auth-library');\n\n// Read the securely-stored service account JSON from environment\nconst credentials = JSON.parse(process.env.FIREBASE_SERVICE_ACCOUNT);\n\nasync function getAccessToken() {\n  const auth = new GoogleAuth({\n    credentials,\n    scopes: ['https://www.googleapis.com/auth/firebase.messaging']\n  });\n  const client = await auth.getClient();\n  const token = await client.getAccessToken();\n  return token.token;\n}\n\nasync function sendPushNotification(fcmToken, title, body) {\n  const accessToken = await getAccessToken();\n  const projectId = credentials.project_id;\n  const url = `https://fcm.googleapis.com/v1/projects/${projectId}/messages:send`;\n  \n  const payload = {\n    message: {\n      token: fcmToken,\n      notification: {\n        title: title,\n        body: body,\n      },\n      // Target specific platform features (e.g., channel on Android, sound on iOS)\n      android: {\n        notification: {\n          channel_id: 'high_importance_channel',\n        }\n      },\n      apns: {\n        payload: {\n          aps: {\n            sound: 'default',\n          }\n        }\n      }\n    }\n  };\n\n  const response = await fetch(url, {\n    method: 'POST',\n    headers: {\n      'Authorization': `Bearer ${accessToken}`,\n      'Content-Type': 'application/json'\n    },\n    body: JSON.stringify(payload)\n  });\n\n  return response.json();\n}\n</code></pre>\n<hr>\n<h2>References</h2>\n<ul>\n<li><a href=\"https://firebase.google.com/docs/cloud-messaging/flutter/client\">Firebase Cloud Messaging Flutter documentation</a></li>\n<li><a href=\"https://firebase.google.com/docs/cloud-messaging/flutter/first-message\">Send a test message to a backgrounded app</a></li>\n<li><a href=\"https://firebase.google.com/docs/cloud-messaging/flutter/receive\">Receive messages in Flutter</a></li>\n<li><a href=\"https://firebase.google.com/docs/cloud-messaging/flutter/topic-messaging\">Topic messaging on Flutter</a></li>\n<li><a href=\"https://firebase.google.com/docs/cloud-messaging/migrate-v1\">Migrate from legacy FCM APIs to HTTP v1</a></li>\n<li><a href=\"https://firebase.google.com/docs/cloud-messaging/auth-server\">Server environment authorization (for OAuth2 / Service Accounts)</a></li>\n<li><a href=\"https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages\">FCM HTTP v1 API Reference</a></li>\n<li><a href=\"https://firebase.google.com/docs/cloud-messaging/android/receive\">Android Receive Docs (for default channel ID)</a></li>\n<li><a href=\"https://pub.dev/documentation/firebase_messaging/latest/firebase_messaging/FirebaseMessaging/setForegroundNotificationPresentationOptions.html\">FirebaseMessaging setForegroundNotificationPresentationOptions (API Reference)</a></li>\n</ul>\n","files":[{"path":"SKILL.md","sizeBytes":12823,"isText":true}],"reviewScore":null,"reviewSummary":null,"trust":{"provenance":"trusted-source-unreviewed","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow.","bodySource":null},"bodyLocked":false,"purchaseUrl":null,"sourceUrl":null,"report":{"provenance":"trusted-source-unreviewed","screen":{"ran":true,"outcome":"notes-only","suspicious":0,"notes":2,"hiddenCharacters":false},"virusScan":{"engine":"clamav","status":"clean","scannedAt":"2026-08-30T09:58:21.073471Z","sha256":"E206E83901B69A7F4A6BE3606540B5BE960280FA850DA2B947355E9C6423D972","sizeBytes":4976},"review":null,"source":{"repositoryUrl":"https://github.com/evanca/flutter-ai-rules","path":"skills/firebase-messaging","license":"MIT","commit":"7b9cce235714ae17acbae896b1e8c8627e128f4c","subtreeSha":"1382D3AC1A2A5B8FA4B913BE609478F8CA5BC7F252E8323DDDB14DCD52E76192","lastSyncedAt":"2026-09-22T13:50:26.675606Z"},"reviewedAt":"2026-08-30T10:14:23.184529Z","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow."},"install":[{"target":"skills-cli","command":"npx skills add https://github.com/evanca/flutter-ai-rules/tree/main/skills/firebase-messaging"},{"target":"claude-code","command":"claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install evanca-flutter-ai-rules@llmmart"},{"target":"git","command":"git clone https://github.com/evanca/flutter-ai-rules.git"}]}