Debug School

rakesh kumar
rakesh kumar

Posted on

A Complete Guide to Firebase Cloud Messaging: Device Token, Token Refresh & Sending Test Notifications

What is a Device Token in FCM?

An FCM Device Token is a unique ID generated by Firebase for every device where your app is installed.

Think of it like:

πŸ‘‰ A mobile number for your app inside Firebase.

Whenever you want to send a push notification to one specific user/device, you use this device token.

βœ” Example:

If you want to send a message to one user only:

Send notification β†’ Firebase β†’ Device Token β†’ User's mobile

πŸ”₯

Key Points:

It is unique per device

It may change at any time

You must store it in your backend (database)

Used to target notifications to specific users

What is Token Refresh?

Sometimes Firebase changes the device token due to:

App reinstall

App data cleared

User logs out & re-logins

Firebase security policy changes

System restores

Update to Play Services / iOS APNs

When this happens, Firebase triggers:

FirebaseMessaging.onTokenRefresh

This gives you a new device token.

Why is token refresh important?

Because old token becomes invalid.

If you don’t update the token:

❌ user will not receive notifications
❌ backend will send to old token β†’ fails
❌ targeted notifications will stop working

So you must listen for token refresh and update it on your server.

When is Device Token Used or Called

?

Device token is used in these scenarios:

βœ” App startup

You fetch the token when app launches.

βœ” After permission is granted

User must allow notifications. Only then token is valid.

βœ” On login

You may map the token to user ID.

βœ” On every token refresh event

Whenever Firebase updates token, you store new token.

βœ” When sending notifications

Your backend uses this token to deliver messages via FCM API.

⭐

Importance of Device Token

Syntax for Device Token & Token Refresh

πŸ‘‰ Request Notification Permission
await _messaging.requestPermission();

πŸ‘‰ Get Device Token
String? token = await _messaging.getToken();

πŸ‘‰ Listen to Token Refresh
_messaging.onTokenRefresh.listen((newToken) {
  print("Token refreshed: $newToken");
});
Enter fullscreen mode Exit fullscreen mode

Function Call (Caller Side – inside initState)

@override
void initState() {
  super.initState();

  // Ask user for notification permission
  _notificationServices.requestNotificationPermission();

  // Listen for auto token refresh
  _notificationServices.listenToTokenRefresh();

  // CALLING getDeviceToken() function
  _notificationServices.getDeviceToken().then((value) {
    print('device token');
    print(value);    // <-- this prints the token returned by the function
  });

  // Foreground notifications
  _notificationServices.firebaseInit(context);

  // Notification click event
  _notificationServices.handleNotificationClick(context);
}

Enter fullscreen mode Exit fullscreen mode

What is happening here?

You call the function _notificationServices.getDeviceToken()

Because it returns a Future, you use .then() to receive the value

Inside .then((value) { ... }), the value = token returned from function

βœ… Function Definition (Callee Side)

This is your actual function that fetches and RETURNS the token:

Future<String?> getDeviceToken() async {
  String? token = await _messaging.getToken();
  print("FCM my Token: $token");
  return token;   // <-- returned back to caller (.then())
}
Enter fullscreen mode Exit fullscreen mode

Full Flow Diagram (Simple)

Easy Explanation

Full Working Code (NotificationServices Class)

Below is the complete copy-paste ready code.

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:app_settings/app_settings.dart';

class NotificationServices {
  final FirebaseMessaging _messaging = FirebaseMessaging.instance;

  /// ----------------------------------------------------
  /// 1. REQUEST NOTIFICATION PERMISSION (Android/iOS)
  /// ----------------------------------------------------
  Future<void> requestNotificationPermission() async {
    NotificationSettings settings = await _messaging.requestPermission(
      alert: true,
      announcement: true,
      badge: true,
      carPlay: false,
      criticalAlert: true,
      provisional: true,
      sound: true,
    );

    if (settings.authorizationStatus == AuthorizationStatus.authorized) {
      print("User granted permission");
    } else if (settings.authorizationStatus == AuthorizationStatus.provisional) {
      print("User granted provisional permission");
    } else {
      print("User denied notification permission");
      AppSettings.openAppSettings();
    }
  }

  /// ----------------------------------------------------
  /// 2. GET THE DEVICE TOKEN
  /// ----------------------------------------------------
  Future<String?> getDeviceToken() async {
    String? token = await _messaging.getToken();
    print("FCM device token: $token");
    return token;
  }

  /// ----------------------------------------------------
  /// 3. LISTEN FOR TOKEN REFRESH
  /// ----------------------------------------------------
  void listenToTokenRefresh() {
    _messaging.onTokenRefresh.listen((newToken) {
      print("πŸ”₯ Token refreshed!");
      print("New Token: $newToken");

      // TODO: Send token to backend API
      // updateTokenOnServer(newToken);
    });
  }

  /// OPTIONAL: Handle foreground & background notifications
  void firebaseInit(BuildContext context) {
    FirebaseMessaging.onMessage.listen((message) {
      print("Notification received in foreground");
      print(message.notification?.title);
      print(message.notification?.body);
    });
  }

  /// When user clicks notification and opens app
  void handleNotificationClick(BuildContext context) {
    FirebaseMessaging.onMessageOpenedApp.listen((message) {
      print("User clicked notification");
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

Using NotificationServices in StatefulWidget

Add this inside your Flutter screen:

final NotificationServices _notificationServices = NotificationServices();

@override
void initState() {
  super.initState();

  // 1. Ask for permission
  _notificationServices.requestNotificationPermission();

  // 2. Listen for token refresh
  _notificationServices.listenToTokenRefresh();

  // 3. Print token for testing
  _notificationServices.getDeviceToken().then((value) {
    print("device token:");
    print(value);
  });

  // 4. Initialize listeners
  _notificationServices.firebaseInit(context);
  _notificationServices.handleNotificationClick(context);
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)