푸시 알림(Firebase Cloud Messaging, FCM)은 모바일 앱 서비스에서 중요한 사용자 소통 수단입니다.
하지만 iOS와 Android는 푸시 알림을 처리하는 방식이 다르고, React Native 기반 앱에서는 추가적인 제약사항이 존재합니다.
이번 글에서는 Android, iOS 각각의 푸시 알림 처리 구조와, React Native 환경에서의 제약과 최종 구현 방안을 정리해봅니다.
{
"to": "device_token",
"notification": {
"title": "Android 알림 제목",
"body": "Android 알림 내용"
},
"data": {
"customKey": "customValue"
}
}
notification 필드가 있으면 OS(Android)가 알림을 시스템 UI로 자동 표시합니다.data 필드만 있는 경우(=data-only 메시지)에도 백그라운드나 완전 종료 상태에서 수신이 가능합니다.{
"apns": {
"headers": {
"apns-priority": "10"
},
"payload": {
"aps": {
"alert": {
"title": "iOS 알림 제목",
"body": "iOS 알림 내용"
},
"mutable-content": 1
},
"customKey": "customValue"
}
}
}
aps.alert가 포함되어야 시스템이 알림을 표시합니다.data-only 메시지를 백그라운드나 종료 상태에서 수신할 수 없습니다.| 앱 상태 | 처리 방식 | 특징 |
|---|---|---|
| 포어그라운드 | 시스템 알림 표시 ❌ | 앱(JS)에서 직접 알림 표시 필요 |
| 백그라운드 | 시스템 알림 표시 ✅ (notification 기반) | |
| 완전 종료(COLD) | 시스템 알림 표시 ✅ |
data-only 메시지도 Native 서비스(MyFirebaseMessagingService)를 이용해 수신 및 처리할 수 있습니다.| 앱 상태 | 처리 방식 | 특징 |
|---|---|---|
| 포어그라운드 | 시스템 알림 표시 ❌ | 앱(JS)에서 직접 알림 표시 필요 |
| 백그라운드 | 시스템 알림 표시 ✅ (aps.alert 기반) | |
| 완전 종료(COLD) | 시스템 알림 표시 ✅ |
aps.alert 또는 FCM의 notification 필드가 반드시 있어야 시스템 알림이 표시됩니다.data-only 메시지는 백그라운드 및 종료 상태에서는 수신할 수 없습니다.React Native는 JS 런타임 위에서 동작하기 때문에, 네이티브 앱과 달리 푸시 수신 구조에 제약이 존재합니다.
| 상태 | 처리 방식 | 주의사항 |
|---|---|---|
| 포어그라운드 | messaging().onMessage() | JS에서 직접 알림 표시 필요 |
| 백그라운드 | 시스템 알림 자동 표시 (notification 포함 시) | 알림 클릭 시 onNotificationOpenedApp() |
| 완전 종료(COLD) | 시스템 알림 표시 | 알림 클릭 시 getInitialNotification() |
setBackgroundMessageHandler는 작동하지 않는다.aps.alert가 포함되어야 한다.React Native 앱에서 푸시 알림 기능을 완성하기 위해서는 두 가지 방안을 고려할 수 있습니다.
notification을 이용한 시스템 알림 처리notification 필드를 반드시 포함시킨다.remoteMessage.data를 통해 앱 안에서 비즈니스 로직을 처리할 수 있다.import messaging from '@react-native-firebase/messaging'
useEffect(() => {
messaging()
.getInitialNotification()
.then(remoteMessage => {
if (remoteMessage) {
// 종료 상태에서 알림 클릭 → 처리
}
})
const unsubscribe = messaging().onNotificationOpenedApp(remoteMessage => {
if (remoteMessage) {
// 백그라운드 상태에서 알림 클릭 → 처리
}
})
return () => unsubscribe()
}, [])
✅ 이 방식은 구현이 가장 간단하고, 앱이 포어그라운드 외의 상태에서도 시스템이 안정적으로 알림을 표시해준다.
알림 클릭 없이도 백그라운드 수신 직후 로직을 처리하거나,
수신 데이터에 따라 알림을 커스터마이징하려면 Native 서비스가 필요하다.
MyFirebaseMessagingService를 직접 구현public class MyFirebaseMessagingService extends FirebaseMessagingService {
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Map<String, String> data = remoteMessage.getData();
String titleKey = data.get("titleKey");
String bodyKey = data.get("bodyKey");
String localizedTitle = getLocalizedString(titleKey);
String localizedBody = getLocalizedString(bodyKey);
}
private String getLocalizedString(String key) {
int resId = getResources().getIdentifier(key, "string", getPackageName());
return resId != 0 ? getString(resId) : key;
}
}
NotificationServiceExtension을 추가class NotificationService: UNNotificationServiceExtension {
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
let bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
if let titleKey = bestAttemptContent?.userInfo["titleKey"] as? String {
bestAttemptContent?.title = NSLocalizedString(titleKey, comment: "")
}
if let bodyKey = bestAttemptContent?.userInfo["bodyKey"] as? String {
bestAttemptContent?.body = NSLocalizedString(bodyKey, comment: "")
}
contentHandler(bestAttemptContent ?? request.content)
}
}
✅ Android와 iOS 모두 수신한 다국어 키를 앱 내 리소스와 매칭하여 로컬라이징 처리할 수 있다.
notification 필드를 포함시키면 Native 코드를 추가하지 않고도 시스템 알림 수신과 처리가 가능하다.