Hey Guys, recently i have integrated FCM( Firebase Cloud Messaging) with one of my spring boot application. So I would like to share my experience with you.
Some Basics Steps You Need To Follow:
1) Please make sure client application registered under Firebase.
2) Now we need to generate Firebase SDK admin key. This is basically JSON file and the purpose of this file is for server side authorization.You can get more details under https://firebase.google.com/docs/cloud-messaging/auth-server .
3) To get Admin SDK go to Project Setting -> Service Accounts -> Check Java radio button -> Finally click on Generate new private key and save it.
4) Now we need to use generated private JSON file in our spring boot application.So for this I am assuming you have already Spring Boot application , if you don't have then you can create from here https://start.spring.io/
5) Create one folder under src/main/resources in your spring boot application. In my case I have created fcm folder. Now put you private key under this folder.
6) In your application.properties file create key/value like app.firebase-configuration-file=fcm/dummy-d7bd4-firebase-adminsdk-l3psc-8c2aa79daa.json
Note : value key contains location of your private JSON file.
7) Add Firebase dependency in pom.xml file.
<dependency>
<groupId>com.google.firebase</groupId>
<artifactId>firebase-admin</artifactId>
<version>6.8.1</version>
</dependency>
8) Now we need to initialize Firebase application , for this we need private key location so to access the location we can use @Value annotation with key name. Create class FCMInitializer with file name FCMInitializer.java
package com.pushnotification.fcm.service;
import java.io.IOException;
import javax.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
@Service
public class FCMInitializer {
@Value("${app.firebase-configuration-file}")
private String firebaseConfigPath;
Logger logger = LoggerFactory.getLogger(FCMInitializer.class);
@PostConstruct
public void initialize() {
try {
FirebaseOptions options = new FirebaseOptions.Builder()
.setCredentials(GoogleCredentials.fromStream(new ClassPathResource(firebaseConfigPath).getInputStream())).build();
if (FirebaseApp.getApps().isEmpty()) {
FirebaseApp.initializeApp(options);
logger.info("Firebase application has been initialized");
}
} catch (IOException e) {
logger.error(e.getMessage());
}
}
}
In the above code snippets , initialize() method is being called on application start up due to @PostConstruct annotaction.
9) Create Class FCMService with @Service annotation which contains some different types of methods. Create class with file name FCMService.java
package com.pushnotification.fcm.service;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import com.google.firebase.messaging.AndroidConfig;
import com.google.firebase.messaging.AndroidNotification;
import com.google.firebase.messaging.ApnsConfig;
import com.google.firebase.messaging.Aps;
import com.google.firebase.messaging.FirebaseMessaging;
import com.google.firebase.messaging.Message;
import com.google.firebase.messaging.Notification;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.pushnotification.fcm.model.PushNotificationRequest;
@Service
public class FCMService {
private Logger logger = LoggerFactory.getLogger(FCMService.class);
public void sendMessage(Map<String, String> data, PushNotificationRequest request)
throws InterruptedException, ExecutionException {
Message message = getPreconfiguredMessageWithData(data, request);
Gson gson = new GsonBuilder().setPrettyPrinting().create();
String jsonOutput = gson.toJson(message);
String response = sendAndGetResponse(message);
logger.info("Sent message with data. Topic: " + request.getTopic() + ", " + response+ " msg "+jsonOutput);
}
public void sendMessageWithoutData(PushNotificationRequest request)
throws InterruptedException, ExecutionException {
Message message = getPreconfiguredMessageWithoutData(request);
String response = sendAndGetResponse(message);
logger.info("Sent message without data. Topic: " + request.getTopic() + ", " + response);
}
public void sendMessageToToken(PushNotificationRequest request)
throws InterruptedException, ExecutionException {
Message message = getPreconfiguredMessageToToken(request);
Gson gson = new GsonBuilder().setPrettyPrinting().create();
String jsonOutput = gson.toJson(message);
String response = sendAndGetResponse(message);
logger.info("Sent message to token. Device token: " + request.getToken() + ", " + response+ " msg "+jsonOutput);
}
private String sendAndGetResponse(Message message) throws InterruptedException, ExecutionException {
return FirebaseMessaging.getInstance().sendAsync(message).get();
}
private AndroidConfig getAndroidConfig(String topic) {
return AndroidConfig.builder()
.setTtl(Duration.ofMinutes(2).toMillis()).setCollapseKey(topic)
.setPriority(AndroidConfig.Priority.HIGH)
.setNotification(AndroidNotification.builder().setSound(NotificationParameter.SOUND.getValue())
.setColor(NotificationParameter.COLOR.getValue()).setTag(topic).build()).build();
}
private ApnsConfig getApnsConfig(String topic) {
return ApnsConfig.builder()
.setAps(Aps.builder().setCategory(topic).setThreadId(topic).build()).build();
}
private Message getPreconfiguredMessageToToken(PushNotificationRequest request) {
return getPreconfiguredMessageBuilder(request).setToken(request.getToken())
.build();
}
private Message getPreconfiguredMessageWithoutData(PushNotificationRequest request) {
return getPreconfiguredMessageBuilder(request).setTopic(request.getTopic())
.build();
}
private Message getPreconfiguredMessageWithData(Map<String, String> data, PushNotificationRequest request) {
return getPreconfiguredMessageBuilder(request).putAllData(data).setToken(request.getToken())
.build();
}
private Message.Builder getPreconfiguredMessageBuilder(PushNotificationRequest request) {
AndroidConfig androidConfig = getAndroidConfig(request.getTopic());
ApnsConfig apnsConfig = getApnsConfig(request.getTopic());
return Message.builder()
.setApnsConfig(apnsConfig).setAndroidConfig(androidConfig).setNotification(
new Notification(request.getTitle(), request.getMessage()));
}
}
10) Now I am going to add one more service layer. Create class PushNotificationService with file name PushNotificationService.java
package com.pushnotification.fcm.service;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.pushnotification.fcm.model.PushNotificationRequest;
@Service
public class PushNotificationService {
private Logger logger = LoggerFactory.getLogger(PushNotificationService.class);
private FCMService fcmService;
public PushNotificationService(FCMService fcmService) {
this.fcmService = fcmService;
}
public void sendPushNotification(PushNotificationRequest request) {
try {
fcmService.sendMessage(getSamplePayloadData(), request);
} catch (Exception e) {
logger.error(e.getMessage());
}
}
public void sendPushNotificationWithoutData(PushNotificationRequest request) {
try {
fcmService.sendMessageWithoutData(request);
} catch (Exception e) {
logger.error(e.getMessage());
}
}
public void sendPushNotificationToToken(PushNotificationRequest request) {
try {
fcmService.sendMessageToToken(request);
} catch (Exception e) {
logger.error(e.getMessage());
}
}
private Map<String, String> getSamplePayloadData() {
Map<String, String> pushData = new HashMap<>();
pushData.put("messageId", "msgid");
pushData.put("text", "txt");
pushData.put("user", "pankaj singh");
return pushData;
}
}
Note : Create these all three above files under the service layer.
11) Now create two files under model layer PushNotificationRequest.java , PushNotificationResponse.java
package com.pushnotification.fcm.model;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Setter
@Getter
@NoArgsConstructor
public class PushNotificationRequest {
private String title;
private String message;
private String topic;
private String token;
}
package com.pushnotification.fcm.model;
public class PushNotificationResponse {
private int status;
private String message;
public PushNotificationResponse() {
}
public PushNotificationResponse(int status, String message) {
this.status = status;
this.message = message;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
12) Create controller class to test push notification.Create file PushNotificationController.java
package com.pushnotification.fcm.controller;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.pushnotification.fcm.model.PushNotificationRequest;
import com.pushnotification.fcm.model.PushNotificationResponse;
import com.pushnotification.fcm.service.PushNotificationService;
@RestController
public class PushNotificationController {
private PushNotificationService pushNotificationService;
public PushNotificationController(PushNotificationService pushNotificationService) {
this.pushNotificationService = pushNotificationService;
}
@PostMapping("/notification/topic")
public ResponseEntity sendNotification(@RequestBody PushNotificationRequest request) {
pushNotificationService.sendPushNotificationWithoutData(request);
return new ResponseEntity<>(new PushNotificationResponse(HttpStatus.OK.value(), "Notification has been sent."), HttpStatus.OK);
}
@PostMapping("/notification/token")
public ResponseEntity sendTokenNotification(@RequestBody PushNotificationRequest request) {
pushNotificationService.sendPushNotificationToToken(request);
return new ResponseEntity<>(new PushNotificationResponse(HttpStatus.OK.value(), "Notification has been sent."), HttpStatus.OK);
}
@PostMapping("/notification/data")
public ResponseEntity sendDataNotification(@RequestBody PushNotificationRequest request) {
pushNotificationService.sendPushNotification(request);
return new ResponseEntity<>(new PushNotificationResponse(HttpStatus.OK.value(), "Notification has been sent."), HttpStatus.OK);
}
}
Finally we have configured everything for push notification.So our code is ready to send the push notification.
We have mainly three use cases:
a) Push notification based on topics. In this kind of notification we don't required any device token. So based on topic configuration push notification will work.
For this you can use end point : /notification/topic
b) Push notification with token.With this end point we can send only title and body part.
For this you can use end point : /notification/token
c) Push notification with additional payload data. With this end point we can send title,body and some additional key/pair data.
For this you can use end point : /notification/data
If you want to test please use postman and pass JSON body like :
{
"title":"Put the push notification title here",
"message":"Put here push notification body here",
"token":"f65ewWeHOUs:APA91bH2mVv-cVpjVv4mJZxkugeNoJJ_AL-Bl_Kfosz26rk1nTJREp-_b9eEzojh3GgvGxtu1VXlyXTxc5j_jQNMl0oBEEw2oe_knHVIpvdAKMG6qya6"
}
Note : You can download full working source code from my repository link https://github.com/firststepitsolution/push-notification-with-fcm-and-spring-boot I have tested on both platform (IOS and Android) and its working as expected.