I’ll explain it in the same style: concept, end-to-end flow, FCM setup, Android 13+ permission, notification channels, handling foreground/background states, and interview points.

Android Push Notification using FCM — Firebase Cloud Messaging.

Think of it like this:

Your App = Student
Backend Server = Teacher
FCM = School Office
Mobile Device = Student's House
FCM Token = Student's address/id card
Notification = Message/letter

When the teacher wants to send a message to one student, the teacher gives the message and address to the school office. The school office delivers it to the correct student’s house.

In Android:

Backend Server gives message + FCM token to Firebase
Firebase delivers message to the correct device + correct app
Android shows notification

Firebase Cloud Messaging supports sending messages to single devices, groups, or topics, and Android apps receive messages using FirebaseMessagingService.


1. Full Push Notification Flow

App installed
|
v
Firebase SDK creates FCM token
|
v
App sends token to backend server
|
v
Backend stores token with userId
|
v
Some event happens
Example: order shipped / new chat message
|
v
Backend sends message to FCM with token
|
v
FCM finds correct mobile device + app
|
v
Android receives message
|
v
Notification shown to user

Simple real example:

User login:
userId = 101
FCM token = abc_xyz_123

Backend stores:
101 -> abc_xyz_123

Later:
Order shipped for user 101

Backend sends:
token = abc_xyz_123
title = Order shipped
body = Your order is on the way

2. Important Terms Explained

1. FCM

FCM means Firebase Cloud Messaging.

It is Google/Firebase service used to send push notifications to Android, iOS, and web apps.

Backend Server -> FCM -> Android Device

2. FCM Token

FCM token is a unique address for one app installation on one device.

Example:

fG45abcXyz123:APA91bH...

It is not a user ID.
It is not a phone number.
It is not an IMEI number.
It is not the device serial number.

It represents something like:

This particular app installation on this particular device

Example:

User installs your app on Samsung phone
-> One FCM token generated

Same user installs your app on OnePlus phone
-> Another FCM token generated

So one user can have multiple FCM tokens.


3. FirebaseMessagingService

This is your Android service class that receives FCM messages.

class MyFirebaseMessagingService : FirebaseMessagingService()

It mainly handles:

onNewToken()
onMessageReceived()

4. Notification Payload

This is message content that Firebase/Android system can show automatically.

Example:

{
"notification": {
"title": "Order shipped",
"body": "Your order is on the way"
}
}

5. Data Payload

This is custom key-value data for your app logic.

Example:

{
"data": {
"screen": "order_details",
"orderId": "123"
}
}

Your app reads this data and decides what to do.


6. Notification Channel

From Android 8.0, API 26, notification channel is required.

Example:

Order Notifications
Chat Notifications
Offer Notifications
Download Notifications

Android documentation says if an app targets Android 8.0 or higher and posts notification without a notification channel, the notification does not appear.


7. POST_NOTIFICATIONS Permission

From Android 13, API 33, app needs runtime permission to show most notifications.

<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

Android documentation confirms this runtime permission was introduced in Android 13 for non-exempt notifications.


3. When is FCM Token Generated?

FCM token can be generated when:

App installed
Firebase SDK initialized
Internet is available
Google Play services available
Firebase project is correctly configured

Usually, token is generated shortly after app first starts.

You can receive it here:

override fun onNewToken(token: String) {
super.onNewToken(token)
}

You can also manually get the current token:

FirebaseMessaging.getInstance().token
.addOnSuccessListener { token ->
Log.d("FCM", "Token: $token")
}

4. When Can Token Change?

Token is not permanent forever.

It can change when:

User reinstalls app
User clears app data
App is restored on new device
Firebase refreshes token
Old token becomes invalid
Security-related token rotation happens

Firebase recommends managing registration tokens properly because stale tokens can become invalid; for Android, FCM considers stale tokens expired after long inactivity, such as 270 days.

That is why we must always handle:

onNewToken(token: String)

5. What Information Does FCM Token Contain?

Student-friendly answer:

FCM token is like a delivery address generated by Firebase.

It is an opaque string. That means your app should not try to decode it.

It does not directly expose:

User name
Phone number
Email
IMEI
Location
Device model

But internally, Firebase/Google can use it to route the message to:

Correct Firebase project
Correct Android app package
Correct app installation
Correct device connection

Your backend should treat token like sensitive delivery information.

Do not print it in production logs.


6. How Does FCM Identify the Correct App?

This is very important.

FCM does not send notification randomly to a phone. It sends to a specific app instance.

Android app is identified using these details:

Firebase project
Application ID / package name
google-services.json configuration
FCM registration token
App installation instance

Example:

Package name:
com.example.shoppingapp

Firebase project:
shopping-app-prod

Token:
abc_xyz_123

When Firebase SDK creates the token, token is linked with:

Firebase project + package name + app installation + device

So when backend sends message to:

abc_xyz_123

FCM already knows:

This token belongs to com.example.shoppingapp
on this specific Android device
under this Firebase project

7. How Mobile Device Receives Notification from FCM

Very simple version:

Android device keeps connection with Google/FCM service
|
v
FCM sends message through Google Play services
|
v
Android system checks target app
|
v
Message is delivered to your app
|
v
Notification is shown

Important point:

Your app does not need to run continuously in background.

Google Play services and Android system help deliver the push message.


8. Student-Friendly Analogy

Suppose you live in a hostel.

Student name = User
Room number = FCM token
Hostel office = FCM
Teacher = Backend server
Student's room = App installation on device

Teacher wants to send a notice to Mahesh.

Teacher gives this to hostel office:

Room number: A-204
Message: Your class starts at 10 AM

Hostel office does not ask every student.

It directly sends message to:

A-204

Similarly:

Backend gives token to FCM
FCM delivers notification to that app installation

9. Pseudocode: Complete Push Notification

App Side

START APP

Initialize Firebase

Get FCM token

IF token received:
Send token to backend with userId

When new token generated:
Send updated token to backend

When message received:
IF app is foreground:
Show custom notification or update UI
ELSE:
Android/FCM may show notification

When user taps notification:
Open target screen

Backend Side

When user logs in:
receive userId and fcmToken
save userId -> fcmToken in database

When event happens:
find userId
get fcmToken from database
create notification payload
send payload to FCM

If FCM says token invalid:
remove token from database

10. Android Setup Step by Step

Step 1: Add Dependency

implementation("com.google.firebase:firebase-messaging")

Usually version is managed using Firebase BoM:

implementation(platform("com.google.firebase:firebase-bom:33.7.0"))
implementation("com.google.firebase:firebase-messaging")

Step 2: Add Permission

In AndroidManifest.xml:

<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

Step 3: Add Service in Manifest

<application>

<service
android:name=".MyFirebaseMessagingService"
android:exported="false">

<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>

</service>

</application>

Meaning:

When FCM message comes, send it to MyFirebaseMessagingService

11. Kotlin Code: Firebase Messaging Service

class MyFirebaseMessagingService : FirebaseMessagingService() {

override fun onNewToken(token: String) {
super.onNewToken(token)

Log.d("FCM", "New token: $token")

// Send latest token to backend
sendTokenToServer(token)
}

override fun onMessageReceived(remoteMessage: RemoteMessage) {
super.onMessageReceived(remoteMessage)

val title = remoteMessage.notification?.title
?: remoteMessage.data["title"]
?: "New Notification"

val body = remoteMessage.notification?.body
?: remoteMessage.data["body"]
?: ""

val screen = remoteMessage.data["screen"]
val orderId = remoteMessage.data["orderId"]

showNotification(title, body, screen, orderId)
}

private fun sendTokenToServer(token: String) {
// API call to backend
// Example:
// POST /save-fcm-token
// userId = loggedInUserId
// token = token
}
}

12. Pseudocode for Token Save API

FUNCTION sendTokenToServer(token):

userId = getLoggedInUserId()

requestBody = {
"userId": userId,
"fcmToken": token,
"deviceType": "android"
}

POST requestBody to backend /save-fcm-token

13. Example Retrofit API

data class FcmTokenRequest(
val userId: String,
val fcmToken: String,
val deviceType: String = "android"
)

interface ApiService {

@POST("save-fcm-token")
suspend fun saveFcmToken(
@Body request: FcmTokenRequest
): Response<Unit>
}

Repository:

class NotificationRepository(
private val apiService: ApiService
) {
suspend fun saveToken(userId: String, token: String) {
apiService.saveFcmToken(
FcmTokenRequest(
userId = userId,
fcmToken = token
)
)
}
}

14. Manually Get Token in Activity/ViewModel

FirebaseMessaging.getInstance().token
.addOnCompleteListener { task ->
if (!task.isSuccessful) {
Log.e("FCM", "Fetching FCM token failed", task.exception)
return@addOnCompleteListener
}

val token = task.result
Log.d("FCM", "Current token: $token")

// Send token to backend
}

When to call this?

After user login
After Firebase initialization
When you want to sync token with backend

15. Create Notification Channel

object NotificationHelper {

const val CHANNEL_ID = "order_channel"

fun createNotificationChannel(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

val channel = NotificationChannel(
CHANNEL_ID,
"Order Notifications",
NotificationManager.IMPORTANCE_HIGH
).apply {
description = "Shows order related updates"
}

val manager = context.getSystemService(NotificationManager::class.java)
manager.createNotificationChannel(channel)
}
}
}

Call it from Application class:

class MyApp : Application() {

override fun onCreate() {
super.onCreate()

NotificationHelper.createNotificationChannel(this)
}
}

Manifest:

<application
android:name=".MyApp">
</application>

16. Show Notification Code

private fun showNotification(
title: String,
body: String,
screen: String?,
orderId: String?
) {
val intent = Intent(this, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
putExtra("screen", screen)
putExtra("orderId", orderId)
}

val pendingIntent = PendingIntent.getActivity(
this,
100,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)

val notification = NotificationCompat.Builder(
this,
NotificationHelper.CHANNEL_ID
)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(title)
.setContentText(body)
.setAutoCancel(true)
.setContentIntent(pendingIntent)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.build()

NotificationManagerCompat.from(this)
.notify(System.currentTimeMillis().toInt(), notification)
}

17. Handle Notification Click

When user taps notification, open specific screen.

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

handleNotificationIntent(intent)
}

override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)

handleNotificationIntent(intent)
}

private fun handleNotificationIntent(intent: Intent?) {
val screen = intent?.getStringExtra("screen")
val orderId = intent?.getStringExtra("orderId")

if (screen == "order_details" && orderId != null) {
openOrderDetails(orderId)
}
}

private fun openOrderDetails(orderId: String) {
// Navigate to OrderDetailsActivity / Fragment / Compose screen
}
}

18. Compose Navigation Example

NavHost(
navController = navController,
startDestination = "home"
) {
composable("home") {
HomeScreen()
}

composable("order/{orderId}") { backStackEntry ->
val orderId = backStackEntry.arguments?.getString("orderId")
OrderDetailsScreen(orderId)
}
}

From notification click:

if (screen == "order_details" && orderId != null) {
navController.navigate("order/$orderId")
}

19. Backend Payload Example

Backend sends this to FCM:

{
"message": {
"token": "USER_DEVICE_FCM_TOKEN",
"notification": {
"title": "Order shipped",
"body": "Your order #123 is on the way"
},
"data": {
"screen": "order_details",
"orderId": "123"
}
}
}

Meaning:

token      -> which app/device should receive message
title -> notification title
body -> notification body
screen -> which screen to open
orderId -> data required by screen

20. Notification Payload vs Data Payload

Notification Payload

{
"notification": {
"title": "New Offer",
"body": "50% discount today"
}
}

Use when:

You only want to show simple notification

Data Payload

{
"data": {
"title": "New Offer",
"body": "50% discount today",
"screen": "offer_details",
"offerId": "OFF123"
}
}

Use when:

You want full control in app code
You want custom navigation
You want custom notification UI
You want to process data silently

Firebase describes notification messages as display messages and data messages as messages where the app decides what happens in code.


21. App State Behavior

App StateWhat Usually Happens
ForegroundonMessageReceived() called
BackgroundSystem may show notification automatically for notification payload
KilledNotification payload may still be displayed by system
Force stoppedDelivery may not happen until app is opened again

Simple explanation:

Foreground:
App is open, so your code handles message.

Background:
App is not visible, so Android system may show notification.

Killed:
System may still deliver notification, depending on app/device state.

Force stopped:
User manually force-stopped app, so Android usually blocks background delivery.

22. Android 13 Permission Code

private val requestNotificationPermission =
registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->

if (isGranted) {
Log.d("Permission", "Notification permission granted")
} else {
Log.d("Permission", "Notification permission denied")
}
}

fun askNotificationPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
requestNotificationPermission.launch(
Manifest.permission.POST_NOTIFICATIONS
)
}
}

Call it after explaining value to user:

"Enable notifications to get order and payment updates."

23. Full Student-Friendly Pseudocode

APP START

create notification channel

IF Android version >= 13:
ask notification permission

Firebase generates token

IF token available:
send token to backend

WHEN token changes:
send new token to backend

WHEN backend wants to notify user:
get user's latest token from database
send message to FCM

FCM receives:
token + title + body + data

FCM checks:
which Firebase project?
which package/app?
which device/app installation?

FCM delivers message to Android device

IF app is foreground:
FirebaseMessagingService.onMessageReceived() runs
app shows notification manually

IF app is background:
Android system may show notification

WHEN user taps notification:
MainActivity opens
app reads extras
app navigates to target screen

24. One-Line Interview Answer

Push notification in Android is usually implemented using FCM. Firebase generates a unique FCM registration token for each app installation. The app sends this token to the backend. When the backend wants to notify a user, it sends a message to FCM using that token. FCM uses the token to identify the correct Firebase project, package name, device, and app instance, then delivers the message to Android. The app receives it through FirebaseMessagingService or Android system displays it depending on app state and payload type.


25. Common Interview Questions

Q1. Is FCM token same for every user?

No.

It is generated for one app installation on one device. One user can have multiple tokens.


Q2. Can FCM token change?

Yes.

It can change after reinstall, clear data, restore, or Firebase token refresh.


Q3. Where should we send FCM token?

To backend server after login or whenever token changes.


Q4. Should backend store old tokens?

Backend should store latest active tokens and remove invalid/stale tokens.


Q5. How does FCM know the app?

Through Firebase project configuration, package name, app instance, and registration token.


Q6. What is needed for Android 13 notifications?

Runtime permission:

POST_NOTIFICATIONS

Q7. What is needed for Android 8+ notifications?

Notification channel.


Q8. Which method receives FCM message?

onMessageReceived(remoteMessage: RemoteMessage)

Q9. Which method receives new token?

onNewToken(token: String)

Q10. Should we send OTP/password in notification?

No.

Do not send sensitive data in notification title/body or payload.


26. Best Practice for Production

Save token after login
Update token in onNewToken()
Store multiple tokens per user
Remove invalid tokens
Use notification channels
Ask Android 13+ permission
Use data payload for custom navigation
Do not send sensitive data
Handle notification tap
Handle foreground and background states
Log delivery/open analytics

Best simple memory line:

FCM token is the address.
Backend is sender.
FCM is delivery service.
Android device receives.
App shows notification.