ZOFTINO.COM android and web dev tutorials

Sending Device Specific Push Notification Message Using Firebase Cloud Messaging

Using Firebase cloud messaging service, app servers can send notification and data messages to client applications such as android app and iOS app. General notifications can be sent to multiple devices using firebase cloud messaging topic and specific notification can be sent to a specific device or client using Firebase SDK generated token on the client.

To know about Firebase Cloud Messaging and how to send messages to FCM topic, please read sending push notifications to multiple clients from app server using firebase cloud messaging and FCM topic.

In this post, you can learn how to send using Firebase cloud messaging a specific notification to a specific client from app server and you can also learn how to process device specific push notification from FCM server on android.

Table of Contents

Sending Device Specific Message from App Server

First in order for your app server program to authenticate itself and get authorization to use FCM, you need to get private key for a service account using Firebase console by going to settings and service account and clicking generate private key button. You can save the resulting private key file in secure location accessible to your app server program.

The server program should use the private key and generate access token using Google API Client library which can be obtained using below maven and gradle entries to your server app build files. Thus generated access key is sent is the message request to FCM server.

<dependency>
     <groupId>com.google.api-client</groupId>
     <artifactId>google-api-client</artifactId>
     <version>1.23.0</version>
   </dependency>
implement 'com.google.api-client:google-api-client:1.23.0'

Next to send device or client specific message to FCM server, it needs registration key generated on the device by FCM SDK. It is the responsibility of client to send the FCM SDK generated registration key to app server and it is app server’s responsibility to save the registration key. You can learn how android app sends the registration key to app server in subsequent sections in this post.

Below example program shows how to generate token, create JSON message and send it to FCM server. It uses OkHttp library for sending message to FCM server and Gson for creating JSON message, so below entries depending on which build system you use, needs to be added to app server project in order for the example code to work.


<dependency>
  <groupId>com.squareup.okhttp3</groupId>
  <artifactId>okhttp</artifactId>
  <version>3.9.1</version>
</dependency>
<dependency>
  <groupId>com.google.code.gson</groupId>
  <artifactId>gson</artifactId>
  <version>2.8.2</version>
</dependency>

implement 'com.squareup.okhttp3:okhttp:3.9.1'
implement 'com.google.code.gson:gson:2.8.2'

Sending Message to FCM Server from App Server Program Example

The server side program generates access key and adds it to http header, creates JSON message containing token which identifies client and sends it to FCM server for delivery to client app.

Your server app should have a service that accepts Firebase registration key from client and stores it in the database so that the program which sends client specific message to FCM server can retrieved it.

It is useful for client apps to send along with registration key any other information that can identify user so that you can retrieve user specific information from server app database and send it in the message to client.

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.logging.Logger;

import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.gson.JsonObject;

import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

public class ClientNotificationsViaFCMServerHelper {

	private static final Logger log = 
		Logger.getLogger(ClientNotificationsViaFCMServerHelper.class.getName());

	private static String SCOPE = "https://www.googleapis.com/auth/firebase.messaging";
	private static String FCM_ENDPOINT = 
			"https://fcm.googleapis.com/v1/projects/yourfirebaseproject/messages:send";

	public static void main(String args[]) {
		ClientNotificationsViaFCMServerHelper fcmMessage =
					 new ClientNotificationsViaFCMServerHelper();

		// fcmMessage.sendNotification();
		fcmMessage.sendData();
	}

	private void sendNotification() {
		String notificationTitle = "New Items In Auto";
		String notificationBody = "New items in auto category have " 
						+ "been added, you may be interested in.";

		sendMessageToFcm(getFCMNotificationMessage(notificationTitle, notificationBody));
	}

	private void sendData() {
		sendMessageToFcm(getFCMDataMessage());
	}

	// send message to firebase cloud messaging server using okhttp
	private void sendMessageToFcm(String jsonMessage) {
		final MediaType mediaType = MediaType.parse("application/json");

		OkHttpClient httpClient = new OkHttpClient();
		try {
			Request request = new Request.Builder().url(FCM_ENDPOINT)
					.addHeader("Content-Type", "application/json; UTF-8")
					.addHeader("Authorization", "Bearer " + getAccessToken())
					.post(RequestBody.create(mediaType, jsonMessage)).build();

			Response response = httpClient.newCall(request).execute();
			if (response.isSuccessful()) {
				log.info("Message has been sent to FCM server " 
							+ response.body().string());
			}

		} catch (IOException e) {
			log.info("Error in sending message to FCM server " + e);
		}

	}

	private static String getAccessToken() throws IOException {
		GoogleCredential googleCredential = GoogleCredential
				.fromStream(new FileInputStream("firebase-priv-key.json"))
				.createScoped(Arrays.asList(SCOPE));
		googleCredential.refreshToken();
		String token = googleCredential.getAccessToken();
		return token;
	}

	private String getFCMDataMessage() {

		Item item = getClientTokenAndData();

		JsonObject jsonObj = new JsonObject();
		jsonObj.addProperty("token", item.getToken());

		JsonObject itemInfo = new JsonObject();
		itemInfo.addProperty("itemName", item.getItemName());
		itemInfo.addProperty("itemPrice", item.getItemPrice());
		itemInfo.addProperty("location", item.getLocation());

		jsonObj.add("data", itemInfo);

		JsonObject msgObj = new JsonObject();
		msgObj.add("message", jsonObj);

		log.info("data  message " + msgObj.toString());

		return msgObj.toString();
	}

	private String getFCMNotificationMessage(String title, String msg) {
		JsonObject jsonObj = new JsonObject();
		// client registration key is sent as token in the message to FCM server
		jsonObj.addProperty("token", getClientToken());

		JsonObject notification = new JsonObject();
		notification.addProperty("body", msg);
		notification.addProperty("title", title);
		jsonObj.add("notification", notification);

		JsonObject message = new JsonObject();
		message.add("message", jsonObj);

		log.info("notification message " + message.toString());

		return message.toString();
	}

	private Item getClientTokenAndData() {
		Item item = new Item();
		item.setToken("ci7d7xsQY24:APA91bGWrwTgA");
		item.setItemName("HP Laptop");
		item.setItemPrice("$1300");
		item.setLocation("Bellevue");
		return item;
	}

	// Firebase SDK registration key from client
	private String getClientToken() {
		return "ci7d7xsQY24:APA91bGWrwTgA";
	}
}

Firebase Cloud Messaging Client Android Project Setup

To create FCM client in your android project, you need to first set up your project by following the steps described at Firebase cloud messaging android project setup.

Android App Firebase Registration Key

Firebase SDK generates a registration key when an app runs first time on the device. Sometimes it may regenerate the key for various reasons. To capture the registration key and send it to app server, you need to create an android service extending FirebaseInstanceIdService service and implementing onTokenRefresh callback which gets called every time a registration token is generated. In onTokenRefresh method, you can get the token using FirebaseInstanceId and calling getToken on it.

In the example service, I used OKHttp to send the registration key to server app.

FirebaseInstanceIdService

 import android.util.Log;

import com.google.firebase.iid.FirebaseInstanceId;
import com.google.firebase.iid.FirebaseInstanceIdService;

import java.io.IOException;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;

public class FirebaseRegistrationTokenService extends FirebaseInstanceIdService {

    private static final String TAG = "FirebaseRegTokenService";
    private static final String SEND_TOKEN_SERVICE_URL =
            "http://yourAppServer/instanceTokenService";


    @Override
    public void onTokenRefresh() {
        String refreshedToken = FirebaseInstanceId.getInstance().getToken();
        Log.d(TAG, "instance id new token is " + refreshedToken);

        sendInstanceIdTokenToServer(refreshedToken);
    }

    private void sendInstanceIdTokenToServer(String token) {
        OkHttpClient httpClient = new OkHttpClient();

        RequestBody formBody = new FormBody.Builder()
                .add("token", token)
                .build();
        Request request = new Request.Builder()
                .url(SEND_TOKEN_SERVICE_URL)
                .post(formBody)
                .build();


        httpClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.e(TAG, "error sending firebase app instance token to app server");
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                ResponseBody responseBody = response.body();
                if (!response.isSuccessful()) {
                    throw new IOException
                            ("Firebase app instance token to server status " + response);
                }

                Log.i(TAG, "Firebase app instance token has been sent to app server "
                        +responseBody.string());
            }
        });
    }
} 

Android FCM Notification Message Example Output

Fcm notification message.

android device specific data message from app server via firebase cloud messaging example

Fcm data message.

android device specific notification message from app server via firebase cloud messaging example

Android FCM Message Handling

Firebase SDK sends intent with message to FirebaseMessagingService service. To handle FCM messages, you need to create an android service extending FirebaseMessagingService and implementing onMessageReceived methods. In the method, you can obtain data and notification messages from RemoteMessage by calling getData and getNotification methods respectively to do further processing

FirebaseMessagingService

 import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.support.v4.app.NotificationCompat;
import android.util.Log;

import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;

import java.util.Map;

import zoftino.com.firestore.R;

public class FcmDeviceSpecificMessageService extends FirebaseMessagingService {

    private static final String TAG = "FcmDeviceSpecificMsgSer";

    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
        Log.d(TAG, "Message from server " + remoteMessage.getFrom());

        if (remoteMessage.getData().size() > 0) {
            sendNotification(null, remoteMessage.getData());
        }
        if (remoteMessage.getNotification() != null) {
            sendNotification(remoteMessage.getNotification(), null);
        }
    }

    private void sendNotification(RemoteMessage.Notification notification, 
                                  Map<String, String> data) {
        String title;
        String body;

        if(notification == null){
            title = "New Item Details";
            body = data.get("itemName")+" "+data.get("itemPrice")+" "+data.get("location");
        }else{
            title = notification.getTitle();
            body  = notification.getBody();
        }


        Intent intent = new Intent(this,  DeviceSepecificMessageFCMActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent,
                PendingIntent.FLAG_ONE_SHOT);

        NotificationCompat.Builder notificationBuilder =
                new NotificationCompat.Builder(this, "fcm-instance-specific")
                        .setSmallIcon(R.drawable.zoftino)
                        .setContentTitle(title)
                        .setContentText(body)
                        .setAutoCancel(true)
                        .setContentIntent(pendingIntent);

        NotificationManager notificationManager =
                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

        notificationManager.notify(0, notificationBuilder.build());
    }
}
 

Android Manifest

 <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="zoftino.com.firestore">
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service
            android:name=".FirebaseRegistrationTokenService">
            <intent-filter>
                <action android:name="com.google.firebase.INSTANCE_ID_EVENT"/>
            </intent-filter>
        </service>
        <service
            android:name=".FcmDeviceSpecificMessageService">
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT"/>
            </intent-filter>
        </service>
    </application>
</manifest>