푸시 알림은 사용자 리텐션의 핵심이며, 포그라운드/백그라운드/종료 상태를 구분해서 처리해야 합니다.

모바일 앱에서 알림은 가장 중요한 기능 중 하나입니다. FCM(Firebase Cloud Messaging)으로 원격 푸시를 보내고, Notifee로 로컬 알림을 표시하는 조합이 일반적입니다.


설치

BASH
# FCM
npm install @react-native-firebase/app @react-native-firebase/messaging

# 로컬 알림 (Notifee)
npm install @notifee/react-native

cd ios && pod install

권한 요청

TSX
import messaging from '@react-native-firebase/messaging';
import { Platform, PermissionsAndroid } from 'react-native';

async function requestNotificationPermission() {
  if (Platform.OS === 'ios') {
    const authStatus = await messaging().requestPermission();
    const enabled =
      authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
      authStatus === messaging.AuthorizationStatus.PROVISIONAL;
    return enabled;
  }

  if (Platform.OS === 'android' && Platform.Version >= 33) {
    const granted = await PermissionsAndroid.request(
      PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS
    );
    return granted === PermissionsAndroid.RESULTS.GRANTED;
  }

  return true;
}

FCM 토큰 관리

TSX
async function setupFCM() {
  // 권한 요청
  const hasPermission = await requestNotificationPermission();
  if (!hasPermission) return;

  // FCM 토큰 가져오기
  const token = await messaging().getToken();
  console.log('FCM Token:', token);

  // 서버에 토큰 저장
  await saveTokenToServer(token);

  // 토큰 갱신 감지
  messaging().onTokenRefresh(async (newToken) => {
    await saveTokenToServer(newToken);
  });
}

알림 수신 처리

TSX
import messaging from '@react-native-firebase/messaging';
import notifee from '@notifee/react-native';

// 1. 포그라운드 알림 처리
function useForegroundNotification() {
  useEffect(() => {
    const unsubscribe = messaging().onMessage(async (remoteMessage) => {
      // 포그라운드에서는 알림이 자동으로 표시되지 않음
      // Notifee로 직접 표시
      const channelId = await notifee.createChannel({
        id: 'default',
        name: '기본 알림',
        importance: 4, // HIGH
      });

      await notifee.displayNotification({
        title: remoteMessage.notification?.title,
        body: remoteMessage.notification?.body,
        android: { channelId },
        data: remoteMessage.data,
      });
    });

    return unsubscribe;
  }, []);
}

// 2. 백그라운드 알림 처리 (index.js에 등록)
messaging().setBackgroundMessageHandler(async (remoteMessage) => {
  console.log('백그라운드 메시지:', remoteMessage);
});

// 3. 알림 탭 처리
function useNotificationTap() {
  useEffect(() => {
    // 앱이 백그라운드일 때 알림을 탭한 경우
    messaging().onNotificationOpenedApp((remoteMessage) => {
      handleNotificationNavigation(remoteMessage.data);
    });

    // 앱이 종료된 상태에서 알림을 탭한 경우
    messaging().getInitialNotification().then((remoteMessage) => {
      if (remoteMessage) {
        handleNotificationNavigation(remoteMessage.data);
      }
    });
  }, []);
}

function handleNotificationNavigation(data: any) {
  // 알림 데이터에 따라 화면 이동
  if (data?.type === 'post') {
    navigation.navigate('PostDetail', { id: data.postId });
  } else if (data?.type === 'chat') {
    navigation.navigate('ChatRoom', { roomId: data.roomId });
  }
}

로컬 알림

TSX
import notifee, { TriggerType, TimestampTrigger } from '@notifee/react-native';

// 즉시 알림 표시
async function showLocalNotification(title: string, body: string) {
  const channelId = await notifee.createChannel({
    id: 'default',
    name: '기본 알림',
  });

  await notifee.displayNotification({
    title,
    body,
    android: {
      channelId,
      smallIcon: 'ic_notification',
      pressAction: { id: 'default' },
    },
    ios: {
      sound: 'default',
    },
  });
}

// 예약 알림
async function scheduleNotification(title: string, body: string, date: Date) {
  const channelId = await notifee.createChannel({
    id: 'reminders',
    name: '리마인더',
  });

  const trigger: TimestampTrigger = {
    type: TriggerType.TIMESTAMP,
    timestamp: date.getTime(),
  };

  await notifee.createTriggerNotification(
    {
      title,
      body,
      android: { channelId },
    },
    trigger
  );
}

알림 상태 정리

PLAINTEXT
앱 상태별 알림 동작:

┌─────────────────────────────────────────────┐
│ 포그라운드 (앱 사용 중)                      │
│ → onMessage() 호출                          │
│ → 알림 자동 표시 안됨                        │
│ → Notifee로 수동 표시 필요                   │
├─────────────────────────────────────────────┤
│ 백그라운드 (앱이 뒤에 있음)                  │
│ → 알림 자동 표시                            │
│ → setBackgroundMessageHandler() 호출        │
│ → 탭 시 onNotificationOpenedApp() 호출      │
├─────────────────────────────────────────────┤
│ 종료 상태 (앱이 완전히 꺼짐)                │
│ → 알림 자동 표시                            │
│ → 탭으로 앱 실행 시 getInitialNotification()│
└─────────────────────────────────────────────┘

정리

  • 푸시 알림은 포그라운드, 백그라운드, 종료 상태 를 각각 다르게 처리해야 합니다
  • 포그라운드에서는 FCM이 알림을 자동 표시하지 않으므로 Notifee로 직접 표시 합니다
  • FCM 토큰은 서버에 저장 하고, 갱신 이벤트 를 구독하세요
  • 알림 탭 시 딥링크와 연동 하여 적절한 화면으로 이동시키세요
  • Android 13+에서는 POST_NOTIFICATIONS 권한 을 명시적으로 요청해야 합니다
댓글 로딩 중...