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로 알림을 표시할 수 있습니다.
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 문서
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() 메서드
3) 메시지 전송 환경 구성 : Spring Boot
💡 해당 글에서는 메시지 수신 환경에 대해서만 다루고 있기에 메시지를 전송하는 Spring Boot 설정은 아래의 글로 대체합니다.
4) 알림 요청 & 메시지 수신 환경구성 : React Native
1. 메시지 수신 초기 환경설정
💡 메시지 수신 초기 환경설정
- 기존에 작성한 FCM 초기 환경 설정이 구성되었다는 가정하에 환경을 구성합니다.
- 초기환경설정에 대해 궁금하시면 아래의 링크를 참고하시면 됩니다.
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>;
💡 [참고] displayNotification 함수
- 사용자 기기에 알림을 즉시 표시하거나 업데이트하는 데 사용되는 API입니다.
displayNotification(notification: Notification): Promise<string>;
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에서 푸시 메시지가 전송됨을 확인하였습니다.
💡 [참고] 해당 푸시 메시지 기반으로 예약 알람 혹은 알람 취소, 수정 기능을 처리하는 방법은 아래의 글을 참고하시면 도움이 됩니다.
6) [추가] foreground 상태에서 headup 알람을 받고 싶을때
💡 foreground상태에서 headup 알람을 받고 싶을때
- 앱이 실행중인 상태에서 상위 메시지를 출력하는 방법을 하고 싶을때는 '채널을 생성할때'와 '알람을 표시할때' importance 속성을 AndroidImportance.HIGH으로 지정하면 headup 알람을 확인 받을 수 있습니다.
중요도 속성 | 소리/진동 여부 | 상태표시줄 노출 여부 | 알람 표시 형태 | 설명 |
HIGH | 있음 | 표시 | 상단 표기(headup), 확장된 상태 표시 | - 해당 속성값은 앱 실행중에 상단(headup)내에 표시되며, 상태 표시줄 내에 확장된 상태로 노출이 됩니다. |
DEFAULT | 있음 | 표시 | 확장된 상태 표시 | - 해당 속성값은 상태표시줄내에 확장된 상태로 노출이 됩니다. |
LOW | 없음 | 표시 | 확장된 상태 표시 | - 해당 속성값은 상태표시줄내에 확장된 상태로 노출이 됩니다.(알람/진동은 수행하지 않습니다) |
MIN | 없음 | 표시 안됨 | 접힌 상태 표시 | - 해당 속성값은 상태표시줄내에 표시되지 않으며, 접힌 상태로 노출이 됩니다. |
NONE | 없음 | 표시 안됨 | 표시 안됨 | - 해당 속성값은 애플리케이션내에 알람 표시가 되지 않습니다. |
/**
* 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 파일 내에 구성하여 앱이 최초 실행되는 시점에만 실행을 하도록 합니다.
오늘도 감사합니다. 😀
반응형