[Spring] FCM 푸시 알림 기본 설정 중간 정리(Feat. React Native)

kjoo0·2024년 6월 20일

Spring 이모저모

목록 보기
5/7
post-thumbnail

최종 구현 목표

  • FCM의 Data Message 구조로 푸시 알림을 보내고 받습니다.
  • 클라이언트는 onMessageReceived를 통해 푸시 알림을 원하는 아이콘, 원하는 데이터를 넣어 커스텀할 수 있습니다.
  • 백엔드는 토큰과 Map 형태의 data 두 개의 필드로 메시지를 구성하여 data에 필요한 데이터를 넣어서 보낼 수 있습니다.

추가 고려사항 참고 자료


백엔드 Spring

구현 항목

  • FCMConfig.java
  • FCMData.java
  • FCMSend.java

사용 예시

먼저 포함해야 하는 정보에 맞게 FCMData의 인스턴스 생성 메서드를 선택해 FCMData를 생성합니다.
이후 FCMService의 sendFCM에 해당 FCMData와 보내야 하는 기기의 token 값을 넣어 호출합니다.
그럼 기본적인 리턴값인 success / error를 받습니다.

  • 현재까지는 Controller로 Post 요청을 받아 테스트했는데, 다른 테스트 방법이 있다면,, 헬프미,,,,
    public String sendPushNotification() {
        try {
            FCMData matchFCM = FCMData.instanceOfMatchFCM("123", "Hello, Match!", "2022-06-15T12:34:56");
            fcmService.sendFCM("abcd", matchFCM);
            return "success";
        } catch (Exception e) {
            return "Error";
        }
    }

FCMConfig

firebasApp 관련 설정을 해줍니다.
@Configuration, @Bean으로 init을 수행하고, messaging할 수 있도록 메서드를 추가합니다.

@Configuration
public class FCMConfig {

    @Bean
    public FirebaseApp initializeFirebase() throws IOException {
        InputStream serviceAccount = getClass().getClassLoader().getResourceAsStream("serviceAccountKey.json");

        if (serviceAccount == null) {
            throw new IOException("File not found: serviceAccountKey.json");
        }

        FirebaseOptions options = FirebaseOptions.builder()
                .setCredentials(GoogleCredentials.fromStream(serviceAccount))
                .build();

        return FirebaseApp.initializeApp(options);
    }

    @Bean
    public FirebaseMessaging firebaseMessaging(FirebaseApp firebaseApp) {
        return FirebaseMessaging.getInstance(firebaseApp);
    }
}

FCMData

우리가 클라이언트로 보내줘야 하는 FCM은 알림 종류에 따라 두가지로 나뉩니다.
채팅방 룸 id, 메시지 타입이 필요없는 경우와 필요한 경우죠!

FCM의 Data Message 타입을 사용했습니다.

클라이언트 수신 데이터 예시

{
  "sentTime":1718814481730,
  "data":
  {
    "senderId":"123",
    "message":"Hello, Match!",
    "createdAt":"2022-06-15T12:34:56",
    "additionalData":
    {\"fcmType\":\"match\"}"},
 "messageId":"0:1718814481739128%c6e5ad3bf9fd7ecd",
    "ttl":2419200,
    "from":"929499276402"
  }

그래서 기본적으로 FCMData class는 다음의 멤버변수를 가집니다.

    private final String senderId;
    private final String message;
    private final String createdAt;
    private final Map<String, String> additionalData; //message 별로 추가 필요한 데이터 넣어주기

추가로 필요한 멤버변수는 additionalData에 map key-value로 저장했습니다. 그리고 최종적으로 FCM으로 보낼 때는 Gson을 이용해 json + String 으로 변환해줬습니다!

@Getter
public class FCMData {
    private final String senderId;
    private final String message;
    private final String createdAt;
    private final Map<String, String> additionalData; //message 별로 추가 필요한 데이터 넣어주기

    private FCMData(String senderId, String message, String createdAt) {
        this.senderId = senderId;
        this.message = message;
        this.createdAt = createdAt;
        this.additionalData = new HashMap<>();
        this.additionalData.put("fcmType", "match");
    }

    private FCMData(String senderId, String message, String createdAt, String chatRoomId, String messageType) {
        this.senderId = senderId;
        this.message = message;
        this.createdAt = createdAt;
        this.additionalData = new HashMap<>();
        this.additionalData.put("fcmType", "chat");
        this.additionalData.put("chatRoomId", chatRoomId);
        this.additionalData.put("messageType", messageType);
    }


    /*인연 FCMData 생성자*/
    public static FCMData instanceOfMatchFCM(String senderId, String message, String createdAt) {
        return new FCMData(senderId, message, createdAt);
    }

    /*ChatFCMData 생성자*/
    public static FCMData instanceOfChatFCM(String senderId, String message, String createdAt, String chatRoomId, String messageType) {
        return new FCMData(senderId, message, createdAt, chatRoomId, messageType);
    }


    public Map<String, String> toMap() {
        Gson gson = new Gson();

        Map<String, String> map = new HashMap<>();
        map.put("senderId", this.senderId);
        map.put("message", this.message);
        map.put("createdAt", this.createdAt);
        map.put("additionalData", gson.toJson(this.additionalData));
        return map;
    }
}

FCMDataTest 코드

package com.jungle.chalnaServer.infra.fcm;

import com.jungle.chalnaServer.infra.fcm.dto.FCMData;
import org.junit.jupiter.api.Test;

import java.util.Map;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

public class FCMDataTest {
    @Test
    void TestMatchFCM(){
        FCMData matchFCM = FCMData.instanceOfMatchFCM("123", "Hello, Match!", "2022-06-15T12:34:56");

        assertEquals("123", matchFCM.getSenderId());
        assertEquals("Hello, Match!", matchFCM.getMessage());
        assertEquals("2022-06-15T12:34:56", matchFCM.getCreatedAt());

        // additionalData 확인
        Map<String, String> additionalData = matchFCM.getAdditionalData();
        assertNotNull(additionalData);
        assertEquals("match", additionalData.get("fcmType"));
    }

    @Test
    void testInstanceOfChatFCM() {
        // 생성자 테스트
        FCMData chatFCM = FCMData.instanceOfChatFCM("456", "Hello, Chat!", "2022-06-16T12:34:56", "roomId123", "text");

        assertEquals("456", chatFCM.getSenderId());
        assertEquals("Hello, Chat!", chatFCM.getMessage());
        assertEquals("2022-06-16T12:34:56", chatFCM.getCreatedAt());

        // additionalData 확인
        Map<String, String> additionalData = chatFCM.getAdditionalData();
        assertNotNull(additionalData);
        assertEquals("chat", additionalData.get("fcmType"));
        assertEquals("roomId123", additionalData.get("chatRoomId"));
        assertEquals("text", additionalData.get("messageType"));
    }
}

FCMService

FCM 푸시 알림을 보내야 할 때 호출해서 사용합니다.

@Service
public class FCMService {
    public void sendFCM(String fcmToken, FCMData fcmData) throws Exception{

        Message.Builder messageBuilder = Message.builder()
                .setToken(fcmToken)
                .putAllData(fcmData.toMap());

        Message message = messageBuilder.build();

        String response = FirebaseMessaging.getInstance().send(message);
        System.out.println("Successfully sent message: " + response);

    }
}

클라이언트 React Native

Firebase Console

  • 앱 등록 및 구성 파일 다운로드
  • Firebase SDK json 파일 백엔드로 전달

FCM case 처리

FirebaseMessagingService를 상속받고, onMessageReceived를 sendNotification 등과 같은 메서드를

예시

푸시 알림 클릭 시 해당 채팅방 화면 혹은 알림 센터 화면으로 이동하는 경우 처리 필요
npx react-native link react-native-screens
npx react-native link react-native-safe-area-context

추가될 수 있는 사항

  • 동적 링크
    메시지 data에 딥링크를 추가하여, 앱을 url 형태로 공유했을 때 앱이 바로 열릴 수 있는 기능을 제공합니다.
    사용 예시 : 앱 공유, 푸시 알림 클릭 시 앱 화면으로 이동
    이 경우 앱 설정에서 SHA 인증서 추가가 필요합니다.
    수신받은 FCM의 data.targetScreen 필드의 url로 이동합니다.

FCM

메시지 종류

Notification Message
Data Message

Notification Message

Notification Message는 주로 사용자에게 표시되는 알림을 위해 설계된 메시지 유형입니다. 이 메시지는 기본적으로 시스템 트레이에 표시되며, 사용자가 알림을 클릭하면 애플리케이션이 열립니다.

특징

  • 포그라운드 상태: onMessageReceived 메서드를 통해 수신되고, 개발자가 커스터마이징하여 처리할 수 있습니다.
  • 백그라운드 상태: 시스템이 자동으로 알림을 생성하여 표시합니다. 알림을 클릭하면 애플리케이션이 열리고, 메시지의 데이터는 인텐트를 통해 전달됩니다.

사용 예시

  • 주요 알림: 중요한 이벤트나 업데이트에 대한 알림(예: 새로운 메시지, 친구 요청).
  • 간단한 정보 전달: 사용자가 바로 확인할 수 있는 간단한 정보(예: 뉴스 업데이트, 프로모션).

Data Message

Data Message는 개발자에게 완전한 제어권을 부여하는 메시지 유형입니다. 이 메시지는 애플리케이션에 전달되어 개발자가 직접 처리할 수 있으며, 사용자에게 직접적인 알림을 표시할지 여부를 결정할 수 있습니다.

특징

  • 포그라운드 및 백그라운드 상태: 항상 onMessageReceived 메서드를 통해 수신되며, 개발자가 메시지를 커스터마이징하여 처리할 수 있습니다.
  • 유연성: 개발자는 메시지의 내용을 자유롭게 정의하고 처리할 수 있습니다.

사용 예시

  • 실시간 업데이트: 채팅 애플리케이션에서 새로운 메시지 수신 처리.
  • 데이터 동기화: 백그라운드에서 데이터를 동기화하거나 업데이트하는 작업.
  • 맞춤형 알림: 알림을 커스터마이징하여 특정 이벤트에 대해 사용자에게 더 많은 정보를 제공.

Notification + Data Message

FCM은 또한 Notification Message와 Data Message를 결합한 형태의 메시지를 지원합니다. 이 유형의 메시지는 Notification Message의 장점과 Data Message의 유연성을 모두 제공합니다.

특징

  • 포그라운드 상태: onMessageReceived 메서드를 통해 수신되고, 개발자가 커스터마이징하여 처리할 수 있습니다.
  • 백그라운드 상태: 시스템이 자동으로 알림을 생성하여 표시합니다. 알림을 클릭하면 애플리케이션이 열리고, 데이터는 인텐트를 통해 전달됩니다.

사용 예시

  • 상세한 알림: 중요한 알림과 함께 추가 데이터를 제공하여 사용자가 클릭했을 때 더 많은 정보를 제공.
profile
티스토리 이사 준비 중..

0개의 댓글