Cloud Messaging (FCM)
Every Firebase feature so far waits for TrailLog to ask — signing in, saving a trail, reading a query. But some events genuinely need to reach the user even while they aren't looking at the app at all: their hiking buddy just logged a new trail, say. That's exactly what Firebase Cloud Messaging (FCM) is for — the standard way to send a push notification to an Android app from a backend server, or, for testing, directly from the Firebase Console with no server involved at all.
Handling an Incoming Message: FirebaseMessagingService
To receive pushes, TrailLog extends FirebaseMessagingService and overrides
onMessageReceived(), which fires whenever a message arrives while the app is in
the foreground. Inside it, TrailLog builds and shows a local notification using the exact
channel and builder setup from Notifications &
Channels:
Showing a notification for an incoming push
class TrailLogMessagingService : FirebaseMessagingService() {
override fun onMessageReceived(message: RemoteMessage) {
val title = message.notification?.title ?: "TrailLog"
val body = message.notification?.body.orEmpty()
// Same NotificationCompat.Builder + channel pattern from kotlin-notifications.php
val notification = NotificationCompat.Builder(this, "trail_updates")
.setSmallIcon(R.drawable.ic_trail)
.setContentTitle(title)
.setContentText(body)
.setAutoCancel(true)
.build()
NotificationManagerCompat.from(this)
.notify(2001, notification)
}
override fun onNewToken(token: String) {
// the device's FCM token changed — see the section below
}
}
This service also has to be declared in the manifest with an intent filter for
com.google.firebase.MESSAGING_EVENT so the system knows to route incoming messages to
it.
The Device Token: Identifying Where to Deliver a Message
Every single app installation gets its own unique FCM registration token — a long opaque string identifying that specific app-on-that-specific-device combination as a valid delivery target. Nothing can be pushed to a device without its token, so retrieving it is the first step before any message can be sent:
suspend fun getFcmToken(): String {
return Firebase.messaging.token.await() // same .await() bridge as Firebase Auth
}
On its own, a token is useless — it needs to be sent to your own backend and associated with the currently signed-in user's UID from Firebase Auth. That's what lets a server later say "send a push to whichever device(s) belong to user X," rather than broadcasting blindly to everyone.
A Realistic TrailLog Use Case
Put together, the flow looks like this: TrailLog's backend watches for new Firestore documents in
the trails collection from Cloud Firestore. When a
trail is added, the backend looks up the owner's hiking buddies and sends each of their stored FCM
tokens a push reading something like "Your hiking buddy just logged a new trail!" — and
whichever of their devices are running TrailLog shows that notification, whether the app is
foregrounded, backgrounded, or not running at all.
Notification Messages vs. Data Messages
FCM messages come in two shapes, and the difference genuinely changes how much control your code has:
| Message type | Behavior |
|---|---|
| Notification message | When the app is backgrounded or killed, the system displays it automatically using its own default notification — onMessageReceived() only runs if the app happens to be in the foreground. |
| Data message | Always delivered straight to onMessageReceived(), foreground or background, giving your code full control over whether (and how) to show a notification. |
Choose a notification message for simple alerts where the system's default display is good enough and you don't need custom logic. Choose a data message — as TrailLog's buddy alert effectively is — whenever you need to decide what to show yourself, such as merging several pending updates into one notification, or silently updating local data without showing anything at all.
Sending a Test Push from the Firebase Console
Before wiring up any backend at all, the Firebase Console's Cloud Messaging section
lets you compose and send a notification directly to a specific device's token, or to every
installation of the app. This is the fastest way to confirm the whole chain works end to end — the
manifest registration, FirebaseMessagingService, and the notification channel — before
a single line of server code exists to trigger real pushes.