Set up push on Android
From a new Firebase project to a push on your Android phone: google-services.json, the service account key, the device token, and a first send.
This guide takes an Android app from no push at all to a notification on your own phone. You need a Google account, Android Studio and your SignOps test key. It takes about 20 minutes.
How it fits together: your app gets a token from Firebase and gives it to your server. Your server adds it to SignOps under your own user id. From then on, your server sends to the user id, and SignOps hands the message to Firebase with your project’s key.
1. Make a Firebase project
- Open the Firebase console and choose Create a project (or open the one your app already uses). Google Analytics is optional; push does not need it.
- On the project’s home page, choose Add app, then the Android icon.
- Type your app’s package name: the
applicationIdinapp/build.gradle.kts, for examplecom.kiranaexpress.app. It must match exactly. - Download google-services.json and put it in your app module folder (
app/), next tobuild.gradle.kts.
2. Add Firebase Messaging to the app
The Firebase setup screen shows the current version numbers; use those.
// build.gradle.kts (project)
plugins {
id("com.google.gms.google-services") version "<version>" apply false
}
// app/build.gradle.kts
plugins {
id("com.google.gms.google-services")
}
dependencies {
implementation(platform("com.google.firebase:firebase-bom:<version>"))
implementation("com.google.firebase:firebase-messaging")
}
3. Ask for permission and get the token
Android 13 and later asks the user before an app can show notifications. Ask at a moment the user understands, such as after they place an order, not at first launch.
<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application>
<service android:name=".PushService" android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<!-- The channel and icon used when the system draws the notification -->
<meta-data android:name="com.google.firebase.messaging.default_notification_channel_id" android:value="orders" />
<meta-data android:name="com.google.firebase.messaging.default_notification_icon" android:resource="@drawable/ic_notification" />
</application>
// After sign-in, once the user allows notifications
if (Build.VERSION.SDK_INT >= 33) {
requestPermissions(arrayOf(Manifest.permission.POST_NOTIFICATIONS), 1)
}
FirebaseMessaging.getInstance().token.addOnSuccessListener { token ->
api.saveDevice(token) // your own endpoint, with the signed-in user
}
// PushService.kt: Firebase changes the token now and then
class PushService : FirebaseMessagingService() {
override fun onNewToken(token: String) { api.saveDevice(token) }
}
Make the orders notification channel at app start with NotificationManager.createNotificationChannel. A push can name another channel with android_channel.
4. Add the token on your server
Your endpoint (api.saveDevice above) knows who is signed in. It adds the token under that user’s id. Adding the same token again only updates it.
curl https://api.signops.dev/v1/devices \
-H "Authorization: Bearer $SIGNOPS_KEY" \
-d user_id=usr_8f21c4 -d platform=android -d token=$FCM_TOKEN
# -> { "id": "dev_...", "user_id": "usr_8f21c4", "platform": "android", ... }
Keep the returned id. When the user signs out on that phone, remove it with DELETE /v1/devices/:id so they stop getting their pushes there. With the Node library this is signops.devices.add({ user_id, platform: "android", token }).
5. Give SignOps your Firebase service account
- In the Firebase console, open the gear icon, Project settings, then the Service accounts tab.
- Choose Generate new private key. A JSON file downloads. Keep it private: it can send to every user of the project.
- In the SignOps dashboard, open Push, Setup, and choose Set up on Android.
- Upload the JSON file. SignOps checks it with Google before saving, then shows the project id and Google accepted the key.
The same service account sends to iOS apps that use Firebase too (see iOS push).
6. Send a test
In the same Android panel, type the user id from step 4 under Send a test and choose Send test push, then See what Google said. Or from your server:
curl https://api.signops.dev/v1/push \
-H "Authorization: Bearer $SIGNOPS_KEY" \
-H "content-type: application/json" \
-d '{"user_id":"usr_8f21c4","title":"Your order is out for delivery","body":"Ravi is 10 minutes away.","android_channel":"orders"}'
# -> { "status": "sent", "devices": 1, "sent": 1, "failed": 0 }
A push to one user is sent before the API answers, so sent and failed are the real result. Pushes to a topic or to everyone answer sending, and the result reaches your webhook.
Test and live keys keep separate device lists. A token your server added with the test key gets pushes sent with the test key, and nothing else does. When you switch your server to the live key, devices are added to the live list as users open the app.
7. Before you ship
- Close the app and send again. Pushes that arrive only while the app is open usually mean the phone is stopping it in the background. Xiaomi, Oppo, Vivo and Realme do this by default; see why pushes do not arrive on those phones.
- Open the app with a push. Put a deep link in
link, and read it from the launch intent. - Know when tokens die. When Google says a token is no longer valid (the app was uninstalled), SignOps removes it and posts
device.removedto your webhook. - Count opens (optional). The SignOps Android SDK reports opens with
signops.push.opened(intent). It is not on Maven Central yet; the call it makes isPOST /v1/push/:id/opened, with the id every push carries assignops_id.
When something goes wrong
| What you see | What it means |
|---|---|
SENDER_ID_MISMATCH |
The token is from another Firebase project than the service account |
UNREGISTERED |
The app was uninstalled or its data cleared; the token is removed |
sent: 1 but nothing on the phone |
The app is in the foreground, notifications are off for the app, or the phone is stopping it in the background |
| Upload refused | The file is not a service account key (a google-services.json is not one) |
Every other code Google can answer is in FCM error codes.