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 typeBehavior
Notification messageWhen 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 messageAlways 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.

Note: These are correct, working FCM patterns, but this website's own project doesn't run a live Android Studio build or a real backend server, so no message is actually sent or received on the page itself.

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.

Up next: With the Firebase section complete, Logging & Debugging Tools starts the Testing & Debugging section — the everyday skills for figuring out what an app is actually doing when something goes wrong.