Firebase: Http V1 notification push with Spring Boot Java implemantation to mobile application

{getToc} $title={Table of Contents}

Firebase Push Notification with Spring Boot by using HTTP v1 API


Introduction

In this blog, I'm going to write about creating push nofication service in Spring Boot by using Firebase Migrate from legacy HTTP to HTTP v1.

Apps using the FCM legacy HTTP API should consider migrating to the HTTP v1 API using the instructions in this guide. The HTTP v1 API has these advantages over the legacy API:
  • Better security via access tokens
  • More efficient customization of messages across platforms
  • More extendable and future-proof for new client platform versions
Keep reading in firebase official docs.


1. Create Firebase Account

You need to create firebase account in order to create project in firebase. Go here to create project.

Provide credentials manually

Firebase projects support Google service accounts, which you can use to call Firebase server APIs from your app server or trusted environment. If you're developing code locally or deploying your application on-premises, you can use credentials obtained via this service account to authorize server requests.

To authenticate a service account and authorize it to access Firebase services, you must generate a private key file in JSON format.

To generate a private key file for your service account:


  1. In the Firebase console, open Settings > Service Accounts.
  2. Click Generate New Private Key, then confirm by clicking Generate Key.
  3. Securely store the JSON file containing the key.

Generate  Firebase service account


2. Project Setting 

After you have successfully generated firebase service account, you'll get service account json file, that's the private key file in JSON format.

In my csae, I'm build the service with spring boot maven project, so you need to place that service account json file in the resources package in the project.

place the service account in project path


3. Set Firebase Key 

In your spring boot project, you need to set these firebase keys in the application.properties file (my case) or applicatin.yml...

#Firebaseio HTTP V1
fcm.service.account=service-account.json
fcm.endpoint.url=https://fcm.googleapis.com/v1/projects/gdt-lucky-draw/messages:send
google.scope=https://www.googleapis.com/auth/firebase.messaging


- To authorize access to FCM, request the scope https://www.googleapis.com/auth/firebase.messaging.
- The endpoint URL for the HTTP v1 API https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send
- fcm.service.account is the service account which just have generated in step 1.


4. Add Dependency 

Use credentials to mint access tokens

Use your Firebase credentials together with the Google Auth Library for your preferred language to retrieve a short-lived OAuth 2.0 access token:

If you are using Maven, add this to your pom.xml file (notice that you can replace google-auth-library-oauth2-http with any of google-auth-library-credentials and google-auth-library-appengine, depending on your application needs):

In this case, I'm using spring boot maven project, so we need to add maven dependeny in the project POM file.

<dependency>
  <groupId>com.google.auth</groupId>
  <artifactId>google-auth-library-oauth2-http</artifactId>
  <version>1.3.0</version>
</dependency>



5. Service Implementation

This step, I will do the implementation service. You need to call the properties value which have already set in application properties file in step 3.

@Value("${google.scope}")
private String scopes;

@Value("${fcm.endpoint.url}")
private String fcm_url;

@Value("${fcm.service.account}")
private String fcm_svc;


To get Credentials from a Service Account JSON key use GoogleCredentials.fromStream(InputStream) or GoogleCredentials.fromStream(InputStream, HttpTransportFactory). Note that the credentials must be refreshed before the access token is available.

private String getAccessToken() throws IOException {
	GoogleCredentials googleCredentials = GoogleCredentials
			.fromStream(new FileInputStream(new ClassPathResource(fcm_svc).getFile()))
			.createScoped(Arrays.asList(scopes));
	googleCredentials.refreshIfExpired();
	return googleCredentials.refreshAccessToken().getTokenValue();
}


Send requests

HTTP v1 send requests require an OAuth 2.0 access token. If you are using the Admin SDK to send messages, the library handles the token for you. If you are using the raw protocol, obtain the token as described in this section and add it to the header as Authorization: Bearer <valid Oauth 2.0 token>.

URL url = new URL(fcm_url);
HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
httpURLConnection.setUseCaches(false);
httpURLConnection.setDoInput(true);
httpURLConnection.setDoOutput(true);

httpURLConnection.setRequestMethod("POST");
httpURLConnection.setRequestProperty("Authorization", "Bearer " + getAccessToken());
httpURLConnection.setRequestProperty("Content-Type", "application/json");



Payload of send requests

Example: customizing with platform overrides

In addition to simplifying cross-platform targeting of messages, the HTTP v1 API provides flexibility to customize messages per platform.


{
  "message": {
    "topic": "news",
    "notification": {
      "title": "Breaking News",
      "body": "New news story available."
    },
    "data": {
      "story_id": "story_12345"
    },
    "android": {
      "notification": {
        "click_action": "TOP_STORY_ACTIVITY",
        "body": "Check out the Top Story"
      }
    },
    // For send to iOS
    "apns": {
      "payload": {
        "aps": {
          "category" : "NEW_MESSAGE_CATEGORY"
        }
      }
    }
  }
}


I have created a function to wrape data as Json Object in order to send payload to Http V1 API.

/**
 * Send data message with Firebase Http V1 
 * @param json_data
 * @param json_payload
 * @param userDeviceIdKey
 * @return
 * @throws IOException
 */
private static JSONObject dataMessagesHttpV1(JSONObject json_data, JSONObject json_payload, String userDeviceIdKey) throws IOException {
	JSONObject json = new JSONObject();
	JSONObject json_obj = new JSONObject();
	json_obj.put("token", userDeviceIdKey);
	json_obj.put("data", json_data);
	json_obj.put("apns", json_payload);
	json.put("message", json_obj);
	return json;
}


And in the push notification service method, I have added all the payload data by seperated for each Json Object key value and call the above function dataMessagesHttpV1 like this.

OutputStreamWriter wr = new OutputStreamWriter(httpURLConnection.getOutputStream(), "UTF-8");

JSONObject json_data = new JSONObject();
JSONObject json_payload = new JSONObject();
JSONObject payload = new JSONObject();
JSONObject obj = new JSONObject();

json_data.put("TITLE_EN", title_en);
json_data.put("DESCRIPTION_EN", description_en);
json_data.put("NOTI_ID", String.valueOf(noti_id));

payload.put("TITLE_EN", title_en);
payload.put("DESCRIPTION_EN", description_en);
payload.put("NOTI_ID", String.valueOf(noti_id));

obj.put("content-available", 1);
payload.put("aps", obj);

json_payload.put("payload", payload);

JSONObject req = dataMessagesHttpV1(json_data, json_payload, userDeviceIdKey);
wr.write(req.toJSONString());
wr.flush();
wr.close();


I have do logging implementation in order to pring out the logs after push notification success or faile. 

private final Logger logger = LoggerFactory.getLogger(getClass());

logger.info("===( Start PushNotiHttpV1Service response log )===");
int responseCode = httpURLConnection.getResponseCode();

if (responseCode == 200) {
  String response = inputstreamToString(httpURLConnection.getInputStream());
  logger.info("Response Code : " + responseCode);
  logger.info("Response Message : " + httpURLConnection.getResponseMessage());
  logger.info("Sending 'POST' request to URL : " + url);
  logger.info("Post parameters : " + req);
  logger.info("Message sent to Firebase for delivery, response:");
  logger.info(response);
} else {
	logger.info("Response Code : " + responseCode);
	logger.info("Response Message : " + httpURLConnection.getResponseMessage());
	logger.info("Unable to send message to Firebase:");
  String response = inputstreamToString(httpURLConnection.getErrorStream());
  logger.info(response);
}
logger.info("===( End PushNotiHttpV1Service response log )===");



Full HttpV1 Push Notification Service Implementation 


import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Scanner;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import com.alibaba.fastjson.JSONObject;
import com.google.auth.oauth2.GoogleCredentials;

@Service
public class PushNotiHttpV1Service {
	private final Logger logger = LoggerFactory.getLogger(getClass());
	
	@Value("${google.scope}")
	private String scopes;

	@Value("${fcm.endpoint.url}")
	private String fcm_url;

	@Value("${fcm.service.account}")
	private String fcm_svc;

	// [START use_access_token]
	private String getAccessToken() throws IOException {
		GoogleCredentials googleCredentials = GoogleCredentials
				.fromStream(new FileInputStream(new ClassPathResource(fcm_svc).getFile()))
				.createScoped(Arrays.asList(scopes));
		googleCredentials.refreshIfExpired();
		return googleCredentials.refreshAccessToken().getTokenValue();
	}
	// [END use_access_token]
	
	/**
	 * Send data message with Firebase Http V1 
	 * @param json_data
	 * @param json_payload
	 * @param userDeviceIdKey
	 * @return
	 * @throws IOException
	 */
	private static JSONObject dataMessagesHttpV1(JSONObject json_data, JSONObject json_payload, String userDeviceIdKey) throws IOException {
		JSONObject json = new JSONObject();
		JSONObject json_obj = new JSONObject();
		json_obj.put("token", userDeviceIdKey);
		json_obj.put("data", json_data);
		json_obj.put("apns", json_payload);
		json.put("message", json_obj);
		return json;
	}

	/**
	 * Send request to FCM message using HTTP.
	 * Encoded with UTF-8 and support special characters.
	 * @param fcmMessage Body of the HTTP request.
	 * @throws IOException
	 * @param title_en
	 * @param description_en
	 * @param noti_id
	 * @param userDeviceIdKey
	 * @throws IOException
	 */
	@Async
	public void pushNotificationWithJsonData(String title_en, String description_en, long noti_id, String userDeviceIdKey)
			throws IOException {
		URL url = new URL(fcm_url);
		HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
		httpURLConnection.setUseCaches(false);
		httpURLConnection.setDoInput(true);
		httpURLConnection.setDoOutput(true);

		httpURLConnection.setRequestMethod("POST");
		httpURLConnection.setRequestProperty("Authorization", "Bearer " + getAccessToken());
		httpURLConnection.setRequestProperty("Content-Type", "application/json");
		
		OutputStreamWriter wr = new OutputStreamWriter(httpURLConnection.getOutputStream(), "UTF-8");

		JSONObject json_data = new JSONObject();
		JSONObject json_payload = new JSONObject();
		JSONObject payload = new JSONObject();
		JSONObject obj = new JSONObject();

		json_data.put("TITLE_EN", title_en);
		json_data.put("DESCRIPTION_EN", description_en);
		json_data.put("NOTI_ID", String.valueOf(noti_id));
		
		payload.put("TITLE_EN", title_en);
		payload.put("TITLE_KH", title_kh);
		payload.put("DESCRIPTION_EN", description_en);
		payload.put("NOTI_ID", String.valueOf(noti_id));
		
		obj.put("content-available", 1);
		payload.put("aps", obj);
		
		json_payload.put("payload", payload);
		
		JSONObject req = dataMessagesHttpV1(json_data, json_payload, userDeviceIdKey);
		wr.write(req.toJSONString());
		wr.flush();
        wr.close();
		
        logger.info("===( Start PushNotiHttpV1Service response log )===");
		int responseCode = httpURLConnection.getResponseCode();
		
		if (responseCode == 200) {
	      String response = inputstreamToString(httpURLConnection.getInputStream());
	      logger.info("Response Code : " + responseCode);
		  logger.info("Response Message : " + httpURLConnection.getResponseMessage());
		  logger.info("Sending 'POST' request to URL : " + url);
		  logger.info("Post parameters : " + req);
		  logger.info("Message sent to Firebase for delivery, response:");
	      logger.info(response);
	    } else {
	    	logger.info("Response Code : " + responseCode);
			logger.info("Response Message : " + httpURLConnection.getResponseMessage());
			logger.info("Unable to send message to Firebase:");
	      String response = inputstreamToString(httpURLConnection.getErrorStream());
	      logger.info(response);
	    }
		logger.info("===( End PushNotiHttpV1Service response log )===");
	}
	
	/**
	   * Read contents of InputStream into String.
	   *
	   * @param inputStream InputStream to read.
	   * @return String containing contents of InputStream.
	   * @throws IOException
	   */
	  private static String inputstreamToString(InputStream inputStream) throws IOException {
	    StringBuilder stringBuilder = new StringBuilder();
	    try (Scanner scanner = new Scanner(inputStream)) {
			while (scanner.hasNext()) {
			  stringBuilder.append(scanner.nextLine());
			}
		}
	    return stringBuilder.toString();
	  }

}


Lastly, call push notification service from other class


/*Inject service bean*/
@Autowired
private PushNotiHttpV1Service pHttpV1Service;
---


/**
 *  Push Notification To User
 */
try {
	this.pHttpV1Service.pushNotificationWithJsonData(
		params.get("TITLE_EN").toString(),
		params.get("DESCRIPTION_EN").toString(),
		noti_id,notiToken);
} catch (Exception e) {
	e.printStackTrace();
}

Summary

Congratulations! We have just developed a push notification service using firebase Http V1 API in Spring boot maven project.


Conclusion

Hope this article was helpful.



Previous Post Next Post