PromleeBlog
sitemapaboutMe

posting thumbnail
Expo 푸시 알림 설정부터 전송까지 (A to Z) - expo-notification
Expo Push Notifications Setup to Sending (A-Z) - expo-notification

📅

🚀

들어가기 전에🔗

모바일 앱을 만들다 보면 사용자에게 중요한 정보를 전달하거나, 앱을 다시 사용하도록 유도하기 위해
푸시 알림
기능을 떠올리게 됩니다. 사용자가 앱을 사용하고 있지 않을 때도 메시지를 보낼 수 있는 강력한 기능이죠. 하지만 처음 설정하려면 조금 복잡하게 느껴질 수 있습니다.
오늘은 Expo를 사용하여 React Native 앱을 개발할 때, 이 푸시 알림 기능을 어떻게 설정하고 사용하는지 차근차근 알아보려 합니다. 필요한 도구를 설치하는 것부터 시작해서, 사용자에게 알림 허락을 받고, 고유한 토큰을 발급받아 서버에서 알림을 보내고, 앱에서 알림을 받는 전체 과정을 상세히 다룰 예정입니다.

🚀

푸시 알림, 왜 필요하고 어떻게 동작할까요?🔗

푸시 알림은 사용자의 참여를 유도하고 중요한 정보를 즉시 전달하는 데 매우 효과적인 도구입니다. 새로운 메시지 도착, 이벤트 알림, 프로모션 정보 등을 보내 사용자가 앱을 다시 방문하도록 만들 수 있습니다.
Expo에서 푸시 알림이 동작하는 큰 흐름은 다음과 같습니다.
  1. 사용자 기기에서 실행되는 여러분의 Expo 앱입니다.
  2. 푸시 토큰 획득
    앱은 사용자에게 알림 권한을 요청하고, 허락을 받으면 해당 기기를 식별할 수 있는 고유한
    Expo 푸시 토큰
    을 발급받습니다.
  3. 토큰 서버 저장
    앱은 발급받은 푸시 토큰을 여러분이 관리하는 백엔드 서버로 전송하여 저장합니다. (어떤 사용자에게 어떤 토큰이 발급되었는지 알아야 나중에 특정 사용자에게 알림을 보낼 수 있습니다.)
  4. 알림 전송 요청
    여러분의 서버에서 특정 사용자(들)에게 알림을 보내고 싶을 때, 저장된 푸시 토큰과 메시지 내용을 담아
    Expo 푸시 서비스
    API로 요청을 보냅니다.
  5. Expo 푸시 서비스
    Expo에서 운영하는 서버로, 여러분의 요청을 받아 실제 푸시 알림 전송을 처리합니다.
  6. 네이티브 푸시 서비스 (APNs/FCM)
    Expo 푸시 서비스는 기기 플랫폼에 맞는 네이티브 푸시 서비스(iOS는 APNs, 안드로이드는 FCM)로 알림 메시지를 전달합니다.
  7. 기기 수신
    APNs/FCM은 해당 기기로 푸시 알림을 최종적으로 전달합니다.
  8. 앱 알림 처리
    사용자의 기기에 알림이 도착하면, 앱은 이 알림을 받아서 화면에 표시하거나 특정 동작을 수행할 수 있습니다.
이 모든 과정을 편리하게 구현할 수 있도록 Expo는 expo-notifications 라는 라이브러리를 제공합니다. 이제 이 라이브러리를 사용하여 실제로 구현해 보겠습니다.

🚀

1단계: 필요한 도구 준비하기 (expo-notifications 설치)🔗

가장 먼저, 푸시 알림 관련 기능을 사용하기 위해 expo-notifications 라이브러리를 설치해야 합니다. Expo 프로젝트에서는 네이티브 모듈과의 호환성을 위해 npm이나 yarn 대신 expo install 명령어를 사용하는 것이 좋습니다.
npx expo install expo-notifications expo-device expo-constants
expo-notifications 라이브러리는 사용자의 권한을 요청하고 푸시 알림을 전송하기 위한 ExpoPushToken을 가져오는 데 사용됩니다.
expo-device는 앱이 실제 기기에서 실행 중인지 확인하는 데 사용됩니다.
expo-constants는 앱 구성에서 projectId 값을 가져오는 데 사용됩니다.

🚀

2단계: 사용자에게 알림 허락받기🔗

이제부터 코드 작성이 시작됩니다! 전체 코드는 아래에 있으니 참고해주세요 코드 바로가기
🖐️
안드로이드, IOS 시뮬레이터에서는 작동하지 않습니다. 실 기기를 연결해서 테스트해주세요!
푸시 알림을 보내려면 반드시 사용자로부터 명시적인 권한을 받아야 합니다. 운영체제(iOS, Android)에서 요구하는 사항이며, 사용자 프라이버시 보호를 위해 필수적인 절차입니다.

권한 상태 확인 및 요청🔗

앱이 시작될 때나 특정 기능 사용 전에 현재 알림 권한 상태를 확인하고, 권한이 없다면 사용자에게 요청해야 합니다. expo-notifications 라이브러리의 함수들을 사용합니다.
import * as Device from 'expo-device';
import * as Notifications from 'expo-notifications';
import Constants from 'expo-constants';
 
function handleRegistrationError(errorMessage: string) {
  alert(errorMessage);
  throw new Error(errorMessage);
}
 
async function registerForPushNotificationsAsync() {
  if (Platform.OS === 'android') {
    Notifications.setNotificationChannelAsync('default', {
      name: 'default',
      importance: Notifications.AndroidImportance.MAX,
      vibrationPattern: [0, 250, 250, 250],
      lightColor: '#FF231F7C',
    });
  }
 
  if (Device.isDevice) {
    const { status: existingStatus } = await Notifications.getPermissionsAsync();
    let finalStatus = existingStatus;
    if (existingStatus !== 'granted') {
      const { status } = await Notifications.requestPermissionsAsync();
      finalStatus = status;
    }
    if (finalStatus !== 'granted') {
      handleRegistrationError('Permission not granted to get push token for push notification!');
      return;
    }
    const projectId =
      Constants?.expoConfig?.extra?.eas?.projectId ?? Constants?.easConfig?.projectId;
    if (!projectId) {
      handleRegistrationError('Project ID not found');
    }
    try {
      const pushTokenString = (
        await Notifications.getExpoPushTokenAsync({
          projectId,
        })
      ).data;
      console.log(pushTokenString);
      return pushTokenString;
    } catch (e: unknown) {
      handleRegistrationError(`${e}`);
    }
  } else {
    handleRegistrationError('Must use physical device for push notifications');
  }
}
👨‍💻
위 코드의 YOUR_EXPO_PROJECT_ID 부분은 반드시 여러분의 Expo 프로젝트 ID로 교체해야 합니다. 프로젝트 ID는 app.json 파일의 extra.eas.projectId 필드나 Expo 웹사이트의 프로젝트 설정에서 확인할 수 있습니다.
또는 다음 코드를 사용하여 projectId를 가져올 수 있습니다.
const projectId =
      Constants?.expoConfig?.extra?.eas?.projectId ??
      Constants?.easConfig?.projectId;
앱 접속 시 알림 허용 팝업
앱 접속 시 알림 허용 팝업

🚀

3단계: 고유 식별표, Expo 푸시 토큰 받기🔗

사용자에게 권한을 성공적으로 받았다면, 이제 해당 기기를 식별할 수 있는
Expo 푸시 토큰
을 얻을 차례입니다. 이 토큰은 Expo 푸시 서비스를 통해 특정 기기로 알림을 보내는 데 사용되는 고유한 주소와 같습니다.
Notifications.getExpoPushTokenAsync() 함수를 호출하면 토큰 객체를 얻을 수 있고, 그 안의 data 속성이 실제 토큰 값입니다. (위 registerForPushNotificationsAsync 함수 예시에 포함되어 있습니다.)
registerForPushNotificationsAsync()
  .then(token => 
    // sendTokenToServer(token); // 서버에 토큰 전송하는 함수 (직접 구현 필요)
  )
  .catch((error: any) => console.log(error));
이렇게 얻은 푸시 토큰은 여러분의 백엔드 서버로 전송하여 저장해야 합니다. 어떤 사용자가 어떤 토큰을 가지고 있는지 매핑해두어야 나중에 특정 사용자에게 푸시 알림을 보낼 수 있습니다.
토큰은 사용자가 앱을 삭제 후 재설치하거나 기기를 변경하면 바뀔 수 있으므로, 앱 실행 시마다 확인하고 필요한 경우 서버에 업데이트하는 로직이 필요할 수 있습니다.

🚀

4단계: 앱에서 푸시 알림 받기🔗

푸시 알림을 성공적으로 보냈다면, 이제 앱에서 이 알림을 어떻게 받아서 처리하는지 알아봐야 합니다. 앱의 상태(실행 중, 백그라운드, 종료됨)에 따라 처리 방식이 조금 다릅니다.

앱이 실행 중일 때 알림 받기 (Foreground)🔗

앱이 사용자 눈앞에서 실행 중일 때 알림이 도착하면, 기본적으로는 시스템 알림이 뜨지 않습니다. 하지만 Notifications.addNotificationReceivedListener를 사용하면 알림 데이터를 받아 앱 내에서 원하는 방식으로 처리할 수 있습니다. (예: 인앱 메시지 표시)
notificationListener.current = Notifications.addNotificationReceivedListener(notification => {
  setNotification(notification);
});
return () => {
  notificationListener.current &&
    Notifications.removeNotificationSubscription(notificationListener.current);
};
만약 Foreground 상태에서도 시스템 알림처럼 표시하고 싶다면, 앱 시작 시 Notifications.setNotificationHandler를 설정하여 shouldShowAlert: true 옵션을 주면 됩니다.
Notifications.setNotificationHandler({
  handleNotification: async () => ({
    shouldShowAlert: true,
  }),
});

알림을 탭했을 때 반응하기🔗

사용자가 받은 푸시 알림(앱이 꺼져있거나 백그라운드 상태에서 받은 시스템 알림)을 탭했을 때 어떤 동작을 할지 정의해야 합니다. 예를 들어 특정 화면으로 이동시키거나, 관련 데이터를 보여주는 등의 작업입니다.
이때는 Notifications.addNotificationResponseReceivedListener를 사용합니다. 이 리스너는 사용자가 알림과 상호작용(탭)했을 때 호출되며, 콜백 함수의 인자로 전달되는 response 객체를 통해 알림 정보(response.notification)에 접근할 수 있습니다. 특히 response.notification.request.content.data 안에 서버에서 보낸 추가 데이터가 들어있으므로, 이를 활용하여 분기 처리를 할 수 있습니다. (위 App 컴포넌트 예시 코드 참고)
responseListener.current = Notifications.addNotificationResponseReceivedListener(response => {
  console.log(response);
});
return () => {
  responseListener.current &&
    Notifications.removeNotificationSubscription(responseListener.current);
};

앱이 꺼져있거나 백그라운드일 때의 기본 동작🔗

앱이 완전히 꺼져 있거나 백그라운드 상태일 때 푸시 알림이 도착하면, 운영체제(iOS/Android)의 기본 알림 시스템에 의해 화면 상단이나 잠금 화면 등에 표시됩니다. 이때 알림이 어떻게 보일지(소리, 뱃지, 알림 센터 표시 등)는 Notifications.setNotificationHandler를 통해 앱 시작 시 미리 설정해 둔 방식에 따라 결정됩니다.
Notifications.setNotificationHandler({
  handleNotification: async () => ({
    shouldShowAlert: true,
    shouldPlaySound: true,
    shouldSetBadge: true,
  }),
});
백그라운드에서 알림 도착 시점에 복잡한 로직을 즉시 실행하는 것은 제한적일 수 있습니다. 주로 사용자가 알림을 탭했을 때 addNotificationResponseReceivedListener를 통해 후속 처리를 하는 것이 일반적인 패턴입니다.

🚀

5단계: 네이티브 설정하기 (중요!)🔗

지금까지 설명한 내용은 주로 Expo Go 앱 환경에서의 테스트를 가정한 것입니다.
하지만 여러분의 앱을 스토어에 출시하기 위한
독립 실행형 앱(Standalone App)
을 빌드하려면, 각 플랫폼(Android, iOS)별 네이티브 푸시 알림 설정을 반드시 완료해야 합니다.
이 설정을 하지 않으면 실제 배포된 앱에서는 푸시 알림이 동작하지 않습니다!
Expo에서는 EAS(Expo Application Services)를 통해 이 과정을 간편하게 처리할 수 있도록 돕습니다.

안드로이드: FCM 설정하기🔗

안드로이드 푸시 알림은
FCM(Firebase Cloud Messaging)
을 통해 전달됩니다.
  1. Firebase 프로젝트 생성
    Google Firebase 콘솔(https://console.firebase.google.com/)에서 새 프로젝트를 만들거나 기존 프로젝트를 사용합니다.
  2. Android 앱 추가
    Firebase 프로젝트 설정에서 Android 앱을 추가하고, 앱의 패키지 이름(app.jsonandroid.package)을 정확히 입력합니다.
  3. google-services.json 파일 다운로드
    앱 추가 과정에서 google-services.json 파일을 다운로드합니다. 이 파일에는 FCM 설정을 위한 정보가 들어있습니다.
  4. FCM 서버 키 확인
    Firebase 프로젝트 설정 > 클라우드 메시징 탭에서 '서버 키'를 확인합니다. (현재 Expo에서는 주로 서버 키 대신 google-services.json 파일을 직접 업로드하는 방식을 사용합니다.)
  5. EAS CLI로 자격 증명 업로드
    프로젝트 디렉토리에서 EAS CLI를 사용하여 다운로드한 google-services.json 파일을 Expo 서버에 업로드합니다.
    # 아직 설치하지 않았다면: npm install -g eas-cli
    eas login # Expo 계정 로그인
    eas credentials 
    Android
    -> Google Service Account
    -> Manage your Google Service Account for Push Notifications (FCM V1)
    -> Set up a Google Service Account Key for Push Notifications (FCM V1)
    -> google account 선택
    이렇게 업로드된 자격 증명은 EAS Build 시 자동으로 앱에 포함됩니다.
  6. app.json 설정 확인
    app.json 파일 내에 android.googleServicesFile 경로가 올바르게 지정되어 있는지 확인합니다.
    app.json
    {
    "expo": {
      "android": {
          "googleServicesFile": "./path/to/google-services.json"
        }
      }
    }

iOS: APNs 설정하기🔗

iOS 푸시 알림은
APNs
(Apple Push Notification service)를 통해 전달됩니다.
  1. Apple Developer 계정
    유료 Apple Developer 계정이 필요합니다.
  2. Bundle Identifier 설정
    앱의 고유 식별자인 Bundle Identifier를 설정하고 Apple Developer 사이트에 등록해야 합니다 (app.jsonios.bundleIdentifier).
  3. Push Notifications 기능 활성화
    Apple Developer 사이트에서 해당 앱 ID(Bundle Identifier)에 대해 Push Notifications 기능을 활성화합니다.
  4. APNs 인증 키 생성 (.p8 파일)
    Apple Developer 사이트의 'Certificates, Identifiers & Profiles' > 'Keys' 섹션에서 'Apple Push Notifications service (APNs)' 기능이 활성화된 새로운 키를 생성하고 .p8 파일을 다운로드합니다. (인증서 방식(.p12)도 가능하지만, 키 방식(.p8)이 더 권장됩니다.) Key ID와 Team ID도 함께 기록해 둡니다.
  5. EAS CLI로 자격 증명 업로드
    프로젝트 디렉토리에서 EAS CLI를 사용하여 다운로드한 .p8 파일과 관련 정보(Key ID, Team ID, Bundle Identifier)를 Expo 서버에 업로드합니다.
    eas credentials # 명령 실행 후 플랫폼(ios) 선택, 안내에 따라 Push Key (.p8) 파일 경로 및 관련 정보 입력
    마찬가지로 이 자격 증명은 EAS Build 시 사용됩니다.
  6. app.json 설정 확인
    ios.bundleIdentifier가 Apple Developer 사이트에 등록된 값과 일치하는지, ios.usesAppleSignIn 등의 설정이 올바른지 확인합니다. ios.entitlementsaps-environment 값이 'development' 또는 'production'으로 빌드 환경에 맞게 설정되는지도 중요합니다. (EAS Build가 관리해주는 경우가 많음)
네이티브 설정은 다소 복잡해 보일 수 있지만, Expo 문서와 EAS CLI의 안내를 잘 따르면 충분히 완료할 수 있습니다.

🚀

6단계: 푸시 알림 보내보기 (테스트 및 실제 전송)🔗

이제 토큰까지 얻었으니, 실제로 푸시 알림을 보내볼 차례입니다.

가장 쉬운 방법: Expo 푸시 알림 도구 사용하기🔗

개발 및 테스트 단계에서는 Expo에서 제공하는 웹 기반 푸시 알림 도구를 사용하는 것이 가장 간편합니다.
  1. Expo Push Notifications Tool 접속
    https://expo.dev/notifications 로 이동합니다.
  2. 푸시 토큰 입력
    방금 여러분의 앱에서 얻은 Expo 푸시 토큰 (ExponentPushToken[...] 형태)을 입력합니다.
  3. 메시지 작성
    알림 제목과 본문 내용을 입력합니다. 필요하다면 data 필드에 JSON 형식의 추가 데이터를 넣을 수 있습니다. (이 데이터는 앱에서 알림을 받았을 때 활용 가능합니다.)
  4. 전송
    'Send Notification' 버튼을 누르면 해당 토큰을 가진 기기로 알림이 발송됩니다.
이 도구를 사용하면 서버 설정 없이도 푸시 알림 수신 및 처리 로직을 테스트해볼 수 있습니다.

서버에서 직접 보내기 - Node.js🔗

실제 서비스에서는 여러분의 백엔드 서버에서 특정 이벤트가 발생했을 때(예: 새로운 메시지 도착, 공지사항 등록 등) 사용자에게 푸시 알림을 보내야 합니다. 이를 위해서는 Expo 푸시 서비스 API로 HTTP POST 요청을 보내야 합니다.
다음은 Node.js 환경에서 fetch API를 사용하여 푸시 알림을 보내는 간단한 예시입니다.
// 이 코드는 백엔드 서버(Node.js 등)에서 실행되어야 합니다.
// 절대로 앱 클라이언트 코드에 API 키나 토큰을 직접 넣지 마세요!
const sendPushNotification = async (expoPushToken, title, body, data) => {
  const message = {
    to: expoPushToken, // 알림을 받을 대상 토큰 (배열로 여러 명에게 동시 전송 가능)
    sound: 'default',  // 알림 도착 시 소리 ('default' 또는 null)
    title: title,      // 알림 제목
    body: body,        // 알림 본문
    data: data,        // 앱에서 추가적으로 사용할 데이터 (JSON 객체)
  };
  try {
    const response = await fetch('https://exp.host/--/api/v2/push/send', {
      method: 'POST',
      headers: {
        'Accept': 'application/json',
        'Accept-encoding': 'gzip, deflate',
        'Content-Type': 'application/json',
        // 'Authorization': `Bearer YOUR_EXPO_ACCESS_TOKEN` // Access Token 방식 사용 시 (선택 사항)
      },
      body: JSON.stringify(message),
    });
    const result = await response.json();
    console.log('Push notification sent:', result);
    // result 객체 예시: { data: { status: 'ok', id: '...' } }
    // status가 'error'인 경우 details 객체에 오류 정보 포함
    //   } catch (error) {
    console.error('Error sending push notification:', error);
  }
};
 
// 사용 예시: 저장된 사용자 토큰으로 알림 보내기
const userToken = 'ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]'; // 실제 사용자 토큰 사용
 
sendPushNotification(
  userToken,
  '새로운 메시지',
  '친구가 메시지를 보냈습니다!',
  { messageId: 123, sender: '친구A' } // 앱에서 활용할 추가 데이터
);
🖐️
Expo Access Token을 사용하는 경우 등 민감한 정보는 절대로 앱 클라이언트 코드에 포함하면 안 됩니다. 항상 보안이 확보된 백엔드 서버에서 API 요청을 보내야 합니다.

🚀

알림 꾸미기와 관리🔗

expo-notifications 라이브러리는 알림을 더 효과적으로 사용하기 위한 추가 기능들도 제공합니다.

🚀

결론🔗

전체 코드
import React, { useState, useEffect, useRef } from 'react';
import { Text, View, Button, Platform } from 'react-native';
import * as Device from 'expo-device';
import * as Notifications from 'expo-notifications';
import Constants from 'expo-constants';
 
// 앱이 실행되는 동안 알림이 도착했을 때 어떻게 처리할지 설정 (Foreground 알림 처리)
// 소리 재생, 뱃지 표시, 알림 표시 여부 등을 설정할 수 있습니다.
Notifications.setNotificationHandler({
  handleNotification: async () => ({
    shouldShowAlert: true, // 알림 도착 시 사용자에게 표시할지 여부
    shouldPlaySound: true, // 소리 재생 여부
    shouldSetBadge: true,  // 뱃지 아이콘 표시 여부 (iOS)
  }),
});
 
// 푸시 알림 권한 요청 및 토큰 받기 함수
async function registerForPushNotificationsAsync() {
  // 안드로이드 채널 설정 (선택 사항이지만 권장됨)
  if (Platform.OS === 'android') {
    Notifications.setNotificationChannelAsync('default', {
      name: 'default',
      importance: Notifications.AndroidImportance.MAX, // 알림 중요도 설정
      vibrationPattern: [0, 250, 250, 250], // 진동 패턴
      lightColor: '#FF231F7C', // 알림 LED 색상
    });
  }
 
  if (Device.isDevice) {
    // 현재 권한 상태 확인
    const { status: existingStatus } = await Notifications.getPermissionsAsync();
    let finalStatus = existingStatus;
 
    // 권한이 부여되지 않았다면 요청
    if (existingStatus !== 'granted') {
      const { status } = await Notifications.requestPermissionsAsync();
      finalStatus = status;
    }
 
    // 최종적으로 권한이 거부되었다면 함수 종료
    if (finalStatus !== 'granted') {
      handleRegistrationError('Permission not granted to get push token for push notification!');
      return;
    }
 
    // 프로젝트 id 추출출
    const projectId =
      Constants?.expoConfig?.extra?.eas?.projectId ?? Constants?.easConfig?.projectId;
    if (!projectId) {
      handleRegistrationError('Project ID not found');
    }
 
    // Expo 푸시 토큰 받기
    try {
      const pushTokenString = (
        await Notifications.getExpoPushTokenAsync({
          projectId,
        })
      ).data;
      console.log(pushTokenString);
      return pushTokenString;
    } catch (e: unknown) {
      handleRegistrationError(`${e}`);
    }
  } else {
    handleRegistrationError('Must use physical device for push notifications');
  }
}
 
export default function App() {
  const [notification, setNotification] = useState<Notifications.Notification | null>(null);
  const notificationListener = useRef<Notifications.Subscription>();
  const responseListener = useRef<Notifications.Subscription>();
 
  useEffect(() => {
    // 1. 알림 권한 요청
    registerForPushNotificationsAsync()
      .then(token => 
        // sendTokenToServer(token); // 서버에 토큰 전송하는 함수 (직접 구현 필요)
      )
      .catch((error: any) => console.log(error));
 
    // 2. 알림 수신 리스너 등록 (앱 실행 중 알림 도착 시)
    notificationListener.current = Notifications.addNotificationReceivedListener(notification => {
      console.log('알림 수신 (foreground):', notification);
      setNotification(notification);
    });
 
    // 3. 알림 반응 리스너 등록 (사용자가 알림 탭 시)
    responseListener.current = Notifications.addNotificationResponseReceivedListener(response => {
      console.log('알림 반응:', response);
      // 여기서 response.notification.request.content.data 등을 통해
      // 알림에 포함된 데이터에 접근하고 특정 화면으로 이동하는 등의 로직 처리 가능
    });
 
    // 컴포넌트 unmount 시 리스너 해제
    return () => {
      if (notificationListener.current) {
        Notifications.removeNotificationSubscription(notificationListener.current);
      }
      if (responseListener.current) {
        Notifications.removeNotificationSubscription(responseListener.current);
      }
    };
  }, []);
 
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'space-around' }}>
      <Text>푸시 알림 테스트</Text>
      {/* 필요 시 버튼 등으로 알림 보내기 기능 트리거 */}
    </View>
  );
}
오늘은 Expo를 사용하여 React Native 앱에 푸시 알림 기능을 추가하는 전체 과정을 단계별로 살펴보았습니다.
  1. expo-notifications 라이브러리를
    설치
    하고,
  2. 사용자에게 알림
    권한
    을 요청하며,
  3. 고유한
    Expo 푸시 토큰
    을 발급받아 서버에 저장하고,
  4. Expo 푸시 알림 도구나 자체
    서버
    를 통해 알림을
    전송
    하며,
  5. 앱에서 알림을
    수신
    하고 사용자의 반응을 처리하는 방법을 배웠습니다.
  6. 마지막으로,
    네이티브 설정
    (FCM, APNs) 과정을 통해 실제 배포될 앱을 준비하는 방법까지 알아보았습니다.

참고🔗