Android/iOS 푸시 알림 구현하기 + React Native

G·2025년 5월 13일

푸시 알림(Firebase Cloud Messaging, FCM)은 모바일 앱 서비스에서 중요한 사용자 소통 수단입니다.
하지만 iOS와 Android는 푸시 알림을 처리하는 방식이 다르고, React Native 기반 앱에서는 추가적인 제약사항이 존재합니다.
이번 글에서는 Android, iOS 각각의 푸시 알림 처리 구조와, React Native 환경에서의 제약과 최종 구현 방안을 정리해봅니다.


1. Android/iOS 푸시 알림 처리 방식

1-1. FCM 페이로드 구조

Android용 페이로드 예시

{
  "to": "device_token",
  "notification": {
    "title": "Android 알림 제목",
    "body": "Android 알림 내용"
  },
  "data": {
    "customKey": "customValue"
  }
}
  • notification 필드가 있으면 OS(Android)가 알림을 시스템 UI로 자동 표시합니다.
  • data 필드만 있는 경우(=data-only 메시지)에도 백그라운드나 완전 종료 상태에서 수신이 가능합니다.
    다만 수신 후에는 앱이 직접 알림을 표시해야 합니다.

iOS용 페이로드 예시 (APNs 경유)

{
  "apns": {
    "headers": {
      "apns-priority": "10"
    },
    "payload": {
      "aps": {
        "alert": {
          "title": "iOS 알림 제목",
          "body": "iOS 알림 내용"
        },
        "mutable-content": 1
      },
      "customKey": "customValue"
    }
  }
}
  • iOS는 FCM 서버가 APNs 서버를 경유하여 디바이스에 알림을 전송합니다.
  • aps.alert가 포함되어야 시스템이 알림을 표시합니다.
  • iOS는 data-only 메시지를 백그라운드나 종료 상태에서 수신할 수 없습니다.

1-2. Android 푸시 알림 라이프사이클

앱 상태처리 방식특징
포어그라운드시스템 알림 표시 ❌앱(JS)에서 직접 알림 표시 필요
백그라운드시스템 알림 표시 ✅ (notification 기반)
완전 종료(COLD)시스템 알림 표시 ✅
  • Android는 data-only 메시지도 Native 서비스(MyFirebaseMessagingService)를 이용해 수신 및 처리할 수 있습니다.

1-3. iOS 푸시 알림 라이프사이클

앱 상태처리 방식특징
포어그라운드시스템 알림 표시 ❌앱(JS)에서 직접 알림 표시 필요
백그라운드시스템 알림 표시 ✅ (aps.alert 기반)
완전 종료(COLD)시스템 알림 표시 ✅
  • iOS는 aps.alert 또는 FCM의 notification 필드가 반드시 있어야 시스템 알림이 표시됩니다.
  • data-only 메시지는 백그라운드 및 종료 상태에서는 수신할 수 없습니다.
  • 필요하면 NotificationServiceExtension을 추가하여 수신된 알림 내용을 가공할 수 있습니다.

2. React Native 환경에서의 제약

React Native는 JS 런타임 위에서 동작하기 때문에, 네이티브 앱과 달리 푸시 수신 구조에 제약이 존재합니다.

2-1. React Native 푸시 수신 흐름

상태처리 방식주의사항
포어그라운드messaging().onMessage()JS에서 직접 알림 표시 필요
백그라운드시스템 알림 자동 표시 (notification 포함 시)알림 클릭 시 onNotificationOpenedApp()
완전 종료(COLD)시스템 알림 표시알림 클릭 시 getInitialNotification()

2-2. 주요 제약사항

  • 앱이 완전히 종료되면 setBackgroundMessageHandler는 작동하지 않는다.
  • 백그라운드나 종료 상태에서는 알림 클릭 이후에만 JS 로직이 실행될 수 있다.
  • iOS는 백그라운드 수신 자체가 제한적이며, 반드시 시스템 알림용 aps.alert가 포함되어야 한다.

3. 최종 구현 방안

React Native 앱에서 푸시 알림 기능을 완성하기 위해서는 두 가지 방안을 고려할 수 있습니다.

3-1. notification을 이용한 시스템 알림 처리

  • 서버에서 전송하는 FCM 메시지에 notification 필드를 반드시 포함시킨다.
  • 이 방식이면 별도의 Native 서비스 구현 없이도 시스템이 자동으로 알림을 표시해준다.
  • 사용자가 알림을 클릭하면 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()
}, [])

✅ 이 방식은 구현이 가장 간단하고, 앱이 포어그라운드 외의 상태에서도 시스템이 안정적으로 알림을 표시해준다.


3-2. Native 서비스(워크)를 등록하여 고급 처리

알림 클릭 없이도 백그라운드 수신 직후 로직을 처리하거나,
수신 데이터에 따라 알림을 커스터마이징하려면 Native 서비스가 필요하다.

Android

  • MyFirebaseMessagingService를 직접 구현
  • 앱이 완전히 종료된 상태에서도 data-only 메시지를 수신하고 로직을 처리할 수 있다.
  • 필요하면 Headless JS를 통해 백그라운드에서도 JS 호출 가능
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;
  }
}

iOS

  • 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 코드를 추가하지 않고도 시스템 알림 수신과 처리가 가능하다.
  • 백그라운드 수신 즉시 데이터 로직을 실행하려면 Android는 Native 서비스를, iOS는 NotificationServiceExtension을 추가해야 한다.
  • 필요하면 수신 데이터에 포함된 다국어 키를 통해 title/body를 로컬라이징할 수 있다.
profile
Hello!

0개의 댓글