React & React Native/라이브러리 활용

[RN] React Native Firebase Cloud Message(FCM) 활용하여 푸시 메시지 수신 구성 : Notifee

adjh54 2024. 2. 22. 22:59
반응형
해당 글에서는 실제 FCM을 이용하여 메시지를 전송하고 수신하는 방법에 대한 활용방법에 대해 알아봅니다.


 

 

 

💡 [참고] FCM 관련해서 구성 내용에 대해 궁금하시면 아래의 글을 참고하시면 도움이 됩니다.
분류 링크
FCM Spirng Boot API 구성 : 단순 전송 기능 https://adjh54.tistory.com/432
FCM React Native 초기 환경 설정 https://adjh54.tistory.com/431
FCM React Native Notifee 메시지 수신 구성 https://adjh54.tistory.com/4333
FCM React Natiive Notifee 메시지 전송 및 예약 발송 구성 https://adjh54.tistory.com/434

 

 

 

 

 

1) 전체 환경 구성


1. 공통 환경 & 처리 과정


💡 공통 환경

- React Native, Spring Boot 개발 환경에서 FCM과 통신하여 메시지를 전송하고 응답 결과를 반환받는 구조로 환경이 구성되어 있습니다.

1. React Native 환경에서는 다른 디바이스로 푸시 메시지를 전송하기 위한 데이터를 구성하며, 디바이스는 FCM을 전달받을 리스너를 구성한 상태이어야 합니다.

2. Spring Boot 환경에서는 모바일에서 전달받은 데이터를 기반으로 FCM과 통신하여 Token 기반의 푸시 메시지를 전송합니다.

3. FCM에서는 전달 받은 데이터를 기반으로 푸시메시지를 전달하고 응답 상태를 반환해 줍니다.

 

 

💡 처리 과정

1. A Mobile

- 푸시 메시지를 보내려는 디바이스에서 시작을 합니다.

2. A Mobile → 앱 실행
- 모바일에서 앱을 실행합니다.

3. 디바이스의 FCM 토큰 조회
- 앱을 실행하면 디바이스의 FCM 토큰을 조회하여 임시 저장해 둡니다.

4. FCM 수신 리스너 등록
- FCM 토큰을 조회하는 동시에 FCM을 수신할 수 있는 리스너를 등록합니다.

5. 특정 동작 수행 : API 호출
- 버튼을 누르는 거나 특정 동작을 수행하였을 경우 Spring Boot Server로 푸시 메시지를 요청합니다.

6. [Spring Boot] FcmController → FcmService → FcmServiceImpl
- Spring Boot Server에서는 이를 받아서 FCM으로 통신을 합니다.( https://fcm.googleapis.com/v1/projects/><프로젝트 명>/messages:send 호출)

7. FCM

- 전달받은 데이터를 기반으로 B 모바일로 푸시 메시지를 등록합니다.

 

 

2) Notifee


💡 Notifee

- React Native와 Firebase의 통합하기 위해 권장되는 오픈소스 라이브러리입니다.
- React Native Firebase를 사용하면 FCM과 쉽게 통합하고 Notifee로 알림을 표시할 수 있습니다.
 

Firebase Cloud Messaging (FCM) | Notifee

Integrate Firebase Cloud Messaging and display notifications with Notifee. Firebase Cloud Messaging (FCM) is a messaging solution that lets you reliably send messages at no cost to both Android & iOS devices. Using FCM, you can send data payloads (via a me

notifee.app

 

 

1. 메시지 구독(Message Subscribe)


💡 메시지 구독(Message Subscribe)

- FCM으로부터 메시지를 전달받을 수 있도록 메시지 구독이 필요합니다. 이 메시지 구독은 두 가지 메서드로 제공을 합니다.
메서드 설명
onMessage() 애플리케이션이 활성 상태이거나 포그라운드에 있을 때 FCM 메시지를 처리 함수
setBackgroundMessageHandler() 앱이 종료된 상태일 때 FCM 메시지를 처리 함수
import messaging from '@react-native-firebase/messaging';

// Note that an async function or a function that returns a Promise 
// is required for both subscribers.
const onMessageReceived = async (message: FirebaseMessagingTypes.RemoteMessage) => {
	// Do something
}

const unsubscribe = messaging().onMessage(async remoteMessage => onMessageReceived(remoteMessage));  	// 활성 상태 및 포그라운드 상태일때 FCM 메시지 수신
messaging().setBackgroundMessageHandler(async remoteMessage => onMessageReceived(remoteMessage));       // 앱이 종료된 상태일 때 FCM 메시지 수신

 

 

 

2. 메시지 전송(Sending message)


💡 메시지 전송(Sending message)

- 메시지를 보내려면 각 장치에 고유한 메시징 토큰을 만들어야 합니다.

- 메시지 토큰을 획득한 후에는 데이터 저장소(예: 외부 데이터베이스)에 저장해야 합니다.
- 메시지를 보낼 시간이 되면 데이터 저장소에서 토큰을 읽고 토큰이 할당된 특정 장치로 FCM을 통해 데이터 페이로드가 전송됩니다.

 

 

2.1. 토큰 생성


💡 토큰 생성

- 장치별로 생성된 FCM 토큰을 통해 해당 장치에 메시지를 보내려면 토큰을 사용해야 하므로 백엔드 데이터베이스에 토큰을 저장하는 것이 좋습니다.
- react-native-firebase/messaging 라이브러리를 이용하여 FCM 토큰을 생성합니다.

 

 💡 아래의 예시에서는 getToken() 메서드를 통해서 디바이스 별 FCM Token을 발급받고 API로 토큰을 전송하여 백엔드 데이터베이스로 전송하는 예시입니다.
import messaging from '@react-native-firebase/messaging';

async function onAppBootstrap() {

  // Get the token
  const token = await messaging().getToken();

  // Save the token
  await postToApi('/users/1234/tokens', { token });
}

 

 

💡 [참고] messaging 메서드의 API 문서
 

messaging | React Native Firebase

Copyright © 2017-2020 Invertase Limited. Except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 3.0 License, and code samples are licensed under the Apache 2.0 License. Some partial documentation, under the

rnfirebase.io

 

 

 

2.2. 알림 표출


💡 알림 표출

- notifee 라이브러리의 displayNotification() 메서드를 통해서 알림을 표출합니다.
async function onDisplayNotification() {
    // Request permissions (required for iOS)
    await notifee.requestPermission()

    // Create a channel (required for Android)
    const channelId = await notifee.createChannel({
      id: 'default',
      name: 'Default Channel',
    });

    // Display a notification
    await notifee.displayNotification({
      title: 'Notification Title',
      body: 'Main body content of the notification',
      android: {
        channelId,
        smallIcon: 'name-of-a-small-icon', // optional, defaults to 'ic_launcher'.
        // pressAction is needed if you want the notification to open the app when pressed
        pressAction: {
          id: 'default',
        },
      },
    });
  }

 

💡 [참고] displayNotification() 메서드
 

displayNotification | Notifee

This API is used to display a notification on the users device. All channels/categories should be created before triggering this method during the apps lifecycle. Copyright 2020 - 2021 © Invertase Limited Except as otherwise noted, the content of this pag

notifee.app

 

 

 

3) 메시지 전송 환경 구성 : Spring Boot


💡 해당 글에서는 메시지 수신 환경에 대해서만 다루고 있기에 메시지를 전송하는 Spring Boot 설정은 아래의 글로 대체합니다.
 

[Java] Spring Boot Firebase Cloud Message(FCM) 푸시 메시지 API 구축 : firebase-admin

해당 글에서는 Spring Boot API에서 FCM으로 통신하여 FCM Push Message 전송 API 구축을 하는 방법에 대해 알아봅니다. 1) Firebase 환경설정💡 Firebase 환경설정 - 아래의 공식 사이트에 접근하여 환경설정을

adjh54.tistory.com

 

 

 

4) 알림 요청 & 메시지 수신 환경구성 : React Native


 

1. 메시지 수신 초기 환경설정


💡 메시지 수신 초기 환경설정

- 기존에 작성한 FCM 초기 환경 설정이 구성되었다는 가정하에 환경을 구성합니다.
- 초기환경설정에 대해 궁금하시면 아래의 링크를 참고하시면 됩니다.
 

[RN] Firebase Cloud Message(FCM) 이해 및 환경설정, 간단 테스트: Android

해당 글에서는 Firebase Cloud Message(FCM)에 대해 이해하고 환경을 구성하며 메시지를 수신하는 형태를 테스트하는 환경 구성 방법에 대해 이해를 돕기 위해 작성하였습니다. 1) FCM(Firebase Cloud Message)

adjh54.tistory.com

 

 

 

2. 라이브러리 설치


# Install & setup the app module
$ yarn add @react-native-firebase/app

# Install the messaging module
$ yarn add @react-native-firebase/messaging

# local Notification
$ yarn add @notifee/react-native

 

 

3. 함수 구성 : getFcmToken


💡 함수 구성 : getFcmToken

- Fcm 토큰을 발급받는 함수입니다.
- 해당 함수를 통해 발급받은 FCM 토큰을 API 호출 시 request 값으로 보낼 예정으로 발급받습니다.
/**
 * FCM 토큰을 받습니다.
 */
const getFcmToken = async () => {
    const fcmTokenInfo = await messaging().getToken();
    setFcmToken(fcmTokenInfo)
}

 

 

💡 [참고] getToken 함수

- 이 기기에 대한 FCM 토큰을 반환합니다.
- 선택적으로 자신의 사용 사례에 맞게 사용자 정의 옵션을 지정할 수 있습니다.
getToken(options?: ): Promise<string>;

 

 

 

 

4. 함수 구성: onMessageReceived


 💡 함수 구성: onMessageReceived

- 해당 함수에서는 FCM으로부터 수신을 받기 위한 리스너로 등록을 합니다.



1. 알림 채널을 생성합니다.
- 앱은 알림을 전송하기 전에 알림 채널을 생성해야 합니다. 이 알림 채널은 사용자가 앱의 알림을 관리하는 데 사용하는 인터페이스입니다.
- 각 알림은 특정 채널에 연결되어야 하며 이 채널은 알림이 사용자에게 어떻게 표시될지를 결정합니다.

2. 디바이스에 알림을 표시합니다.
- 파라미터로 전달되는 message 값에서 각각 타이틀과 내용을 전달받아서 사용자 디바이스에 출력합니다.
/**
* FCM 메시지 수신 리스너를 등록합니다. (Foreground, Background 상태)
* @param {FirebaseMessagingTypes.RemoteMessage} message 
*/
const onMessageReceived = async (message: FirebaseMessagingTypes.RemoteMessage) => {
    console.log("title :: ", message.notification!.title);
    console.log("body :: ", message.notification!.body);

    // 알림 채널을 생성합니다.
    const channelId = await notifee.createChannel({
        id: 'default',
        name: 'Default Channel',
    });

    // 디바이스에 알림을 표시합니다.
    await notifee.displayNotification({
        title: message.notification!.title,
        body: message.notification!.body,
        android: {
            channelId: channelId,
            smallIcon: 'ic_launcher',
        },
    });
}

 

💡 [참고] createChannels 함수

 - 지원되는 Android 기기에서 여러 채널을 만들고 업데이트하는 API입니다.
createChannels(channels: AndroidChannel[]): Promise<void>;

 

 

 

createChannels | Notifee

This API is used to perform a single operation to create or update channels. See the createChannel documentation for more information. Copyright 2020 - 2021 © Invertase Limited Except as otherwise noted, the content of this page is licensed under the Crea

notifee.app

 

💡 [참고] displayNotification 함수

- 사용자 기기에 알림을 즉시 표시하거나 업데이트하는 데 사용되는 API입니다.
displayNotification(notification: Notification): Promise<string>;
 

displayNotification | Notifee

This API is used to display a notification on the users device. All channels/categories should be created before triggering this method during the apps lifecycle. Copyright 2020 - 2021 © Invertase Limited Except as otherwise noted, the content of this pag

notifee.app

 

 

 

5. 함수 구성 : sendPushMessage()


💡 sendPushMessage()

- fetch를 이용하여 Spring Boot API로 호출을 하며 푸시 메시지를 전송합니다.
/**
 * 푸시 메시지를 전송합니다.
 */
const sendPushMessage = async () => {

    const sendInfo = {
        token: fcmToken,
        title: "테스트 전송합니다.",
        body: "테스트로 전송하는 내용입니다."
    }

    await fetch('<http://xxx.xxx.xxx.xx:8000/api/v1/fcm/send>', {
        method: "POST",
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(sendInfo)
    })
        // Response 데이터 파싱
        .then((response) => response.json())
        .then((res) => {
            const { result, resultCode } = res;

            console.log(result, resultCode)
        })
        .catch((error) => {
            console.log(`에러가 발생하였습니다 ${error}`)
        });
}

 

 

6. 종합 소스코드


💡 종합 환경 구성

1. useEffect

- 해당 컴포넌트가 호출될 때 getFcmToken 함수를 호출하여 State에 fcmToken을 세팅합니다.
- onMessage() 함수로 foreground 상태일 때 푸시 메시가 들어오면 이를 수신하여 onMessageReceived()를 호출합니다.


2. "알람 전송" 버튼 터치 시

- sendPushMessage() 함수가 호출되며 알림 전송 데이터를 구성하여 fetch를 이용하여 Spring Boot API로 알림 요청이 보내집니다.
- 요청을 하여 결과로 성공 시 값으로 1을 반환받으면 푸시 메시지가 전송 완료됨을 확인할 수 있습니다.
import React, { useEffect, useState } from "react"
import messaging from '@react-native-firebase/messaging';
import notifee from '@notifee/react-native';
import { Text, TouchableHighlight, View } from "react-native";

const NotificationScreen = () => {

    const [fcmToken, setFcmToken] = useState("");

    useEffect(() => {
    
        getFcmToken();
		console.log("[+] FCM 메시지 리스너가 등록되었습니다.!")
		const unsubscribe = messaging().onMessage(async remoteMessage => await onMessageReceived(remoteMessage));  	// 활성 상태 및 포그라운드 상태일때 FCM 메시지 수신

		return () => {
			console.log("[-] FCM 메시지 리스너가 사라졌습니다!")
			unsubscribe();
		}
	}, []);

    /**
     * FCM 토큰을 받습니다.
     */
    const getFcmToken = async () => {
        const fcmTokenInfo = await messaging().getToken();
        setFcmToken(fcmTokenInfo)
    }

    /**
     * FCM 메시지 수신 리스너를 등록합니다. (Foreground, Background 상태)
     * @param {FirebaseMessagingTypes.RemoteMessage} message 
     * @return {Promise<void>}
     */
    const onMessageReceived = async (message: FirebaseMessagingTypes.RemoteMessage) => {
        console.log("title :: ", message.notification!.title);
        console.log("body :: ", message.notification!.body);

        // 알림 채널을 생성합니다.
        const channelId = await notifee.createChannel({
            id: 'default',
            name: 'Default Channel',
        });

        // 디바이스에 알림을 표시합니다.
        await notifee.displayNotification({
            title: message.notification!.title,
            body: message.notification!.body,
            android: {
                channelId: channelId,
                smallIcon: 'ic_launcher',
            },
        });
    }

    /**
     * 푸시 메시지를 전송합니다.
     */
    const sendPushMessage = async () => {

        const sendInfo = {
            token: fcmToken,
            title: "테스트 전송합니다.",
            body: "테스트로 전송하는 내용입니다."
        }

        await fetch('<http://xxx.xxx.xx.xx:8000/api/v1/fcm/send>', {
            method: "POST",
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(sendInfo)
        })
            // Response 데이터 파싱
            .then((response) => response.json())
            .then((res) => {
                const { result, resultCode } = res;

                console.log(result, resultCode)
            })
            .catch((error) => {
                console.log(`에러가 발생하였습니다 ${error}`)
            });
    }

    return (
        <>
            <View>
                <TouchableHighlight onPress={sendPushMessage}>
                    <Text>알람 전송</Text>
                </TouchableHighlight>
            </View>
        </>
    )

}
export default NotificationScreen

 

 

 

5) 결과 확인


 

1. 모바일에서 API 통신 확인


 

💡 모바일에서 API 통신 확인

- 모바일에서 푸시 메시지를 API로 호출하였을 때 result 값으로 1을 반환받아 성공을 확인하였습니다.

 

 

 

2. Spring Boot 서버에서 데이터 확인


💡 Spring Boot 서버에서 데이터 확인

- 모바일에서 데이터를 전달받은 값을 확인하였고 처리 이후 200 값을 반환받음을 확인하였습니다.

 

 

 

3. 모바일 기기에서 푸시 메시지 확인


💡 모바일 기기에서 푸시 메시지 확인

- 서버에서 FCM으로 전송한 데이터를 기반으로 FCM에서 푸시 메시지가 전송됨을 확인하였습니다.

 

 

 

 

💡 [참고] 해당 푸시 메시지 기반으로 예약 알람 혹은 알람 취소, 수정 기능을 처리하는 방법은 아래의 글을 참고하시면 도움이 됩니다.
 

[RN] React Native Notification 푸시 메시지 예약 발송 활용하기 : Notifee Trigger

해당 글에서는 Notifee 라이브러리를 이용하여 예약 발송 방법에 대해 이를 활용하는 방법에 대해 알아봅니다. 1) Notifee 💡 Notifee - React Native와 Firebase의 통합하기 위해 권장되는 오픈소스 라이브

adjh54.tistory.com

 

 

 

6) [추가] foreground 상태에서 headup 알람을 받고 싶을때


💡 foreground상태에서 headup 알람을 받고 싶을때

- 앱이 실행중인 상태에서 상위 메시지를 출력하는 방법을 하고 싶을때는 '채널을 생성할때'와 '알람을 표시할때' importance 속성을 AndroidImportance.HIGH으로 지정하면 headup 알람을 확인 받을 수 있습니다.
중요도 속성 소리/진동 여부 상태표시줄 노출 여부 알람 표시 형태 설명
HIGH 있음 표시 상단 표기(headup), 확장된 상태 표시 - 해당 속성값은 앱 실행중에 상단(headup)내에 표시되며, 상태 표시줄 내에 확장된 상태로 노출이 됩니다.
DEFAULT 있음 표시 확장된 상태 표시 - 해당 속성값은 상태표시줄내에 확장된 상태로 노출이 됩니다.
LOW 없음 표시 확장된 상태 표시 - 해당 속성값은 상태표시줄내에 확장된 상태로 노출이 됩니다.(알람/진동은 수행하지 않습니다)
MIN 없음 표시 안됨 접힌 상태 표시 - 해당 속성값은 상태표시줄내에 표시되지 않으며, 접힌 상태로 노출이 됩니다.
NONE 없음 표시 안됨 표시 안됨 - 해당 속성값은 애플리케이션내에 알람 표시가 되지 않습니다.
 

Appearance | Notifee

Notifications can be displayed to your users in a variety of ways. This page covers how to customise the appearance of notifications. Whilst changing the appearance can improve the overall aesthetics of a notification, it isn't required. A notification wil

notifee.app

 

/**
 * FCM 메시지 수신 리스너를 등록합니다. (Foreground, Background 상태)
 * @param {FirebaseMessagingTypes.RemoteMessage} message 
 * @return {Promise<void>}
 */
const onMessageReceived = async (message: FirebaseMessagingTypes.RemoteMessage): Promise<void> => {
    const { title, body } = message.notification!;
    console.log("[+] 예약된 알람이 도착하였습니다. Title [" + title + "]  Body [" + body + "]");

    // 채널 생성
    const channelIdConfig = await notifee.createChannel({
        id: 'important',
        name: 'Important Notifications',
        importance: AndroidImportance.HIGH,  // 채널 생성시 중요도를 설정해줍니다.
    });

    // 디바이스에 알림을 표시합니다.
    await notifee.displayNotification({
        title,
        body,
        android: {
            channelId: channelIdConfig,
            smallIcon: 'ic_launcher',
            importance: AndroidImportance.HIGH,
            visibility: AndroidVisibility.PUBLIC,
        },
    });
}

 

 

 

 

 

💡 [참고] Notifee AndroidImportance high not working 문제가 발생합니다.

- 해당 속성으로 지정을 했는데도 동일하게 headup 알람이 제공되지 않는다면 공식사이트의 예시와 같게 채널의 아이디와 이름을 맞춰주면 이상하게(?) 되는것 같습니다.
import notifee, { AndroidImportance } from '@notifee/react-native';

const channelId = await notifee.createChannel({
  id: 'important',
  name: 'Important Notifications',
  importance: AndroidImportance.HIGH,
});

await notifee.displayNotification({
  title: 'Your account requires attention',
  body: 'You are overdue payment on one or more of your accounts!',
  android: {
    channelId,
    importance: AndroidImportance.HIGH,
  },
});

 

 

 

7) [추가] Notifee 알람이 중복으로 발생하는 경우


💡[추가] Notifee 알람이 중복으로 발생하는 경우 

- 해당 경우는 back-end 내에서 FCM 전송 알람을 1개로 보냈으나 'Notifee내에서 중복으로 메시지를 출력하는 문제'가 발생하였습니다. 

- 이러한 경우는 결론적으로 'onMessage()'가 중복으로 등록되어 발생하는 문제이며 아래와 같은 경우에 발생하였습니다.

1. useEffect()를 통해서 화면이 렌더링 될때 해당 onMessage() 메서드가 반복되는 경우

2. Node.js에서 Hot Reloading을 통해서 개발 단계에서 반복적으로 onMessage() 메서드가 반복되는 경우 



- 그렇기에 onMessage() Clean Up을 잘 해주어야 하며, 딱 한번만 호출 될 수 있도록 구성해야합니다. 예를 들어서 App.tsx 파일 내에 구성하여 앱이 최초 실행되는 시점에만 실행을 하도록 합니다.

 

 

 

 

 

 

오늘도 감사합니다. 😀

 

 

 

 

반응형