푸시 알림 권한 요청 (expo-notifications)

개요

앱에서 푸시 알림을 보내려면 먼저 사용자에게 권한을 요청해야 합니다. iOS는 명시적 권한이 필수이고, Android는 13+ 버전부터 필요합니다.


설치

npx expo install expo-notifications expo-device expo-linking

주의: 네이티브 모듈이므로 Development Build 재빌드 필요


권한 요청 플로우

권한 요청 버튼 터치
    
물리 디바이스 확인 (시뮤레이터는 푸시 미지원)
    
Android: 알림 채널 설정 (8.0+ 필수)
    
현재 권한 상태 확인
    ├─ granted (이미 허용)  완료
    ├─ undetermined (미결정)  시스템 권한 다이얼로그 표시
    └─ denied (거부됨)  설정 앱으로 이동 안내

구현 코드

// src/utils/notification.ts
import * as Notifications from "expo-notifications";
import * as Device from "expo-device";
import * as Linking from "expo-linking";
import { Alert, Platform } from "react-native";

/**
 * Android 알림 채널 설정
 * Android 8.0 (API 26) 이상에서는 알림 채널이 필수
 */
const setupAndroidNotificationChannel = async (): Promise<void> => {
  if (Platform.OS === "android") {
    await Notifications.setNotificationChannelAsync("default", {
      name: "기본 알림",
      importance: Notifications.AndroidImportance.MAX,
      vibrationPattern: [0, 250, 250, 250],
      lightColor: "#769fcd",
    });
  }
};

/**
 * 권한 거부 시 설정 앱으로 이동 안내
 */
const showPermissionDeniedAlert = (): void => {
  Alert.alert(
    "알림 권한 필요",
    "알림을 받으려면 설정에서 알림 권한을 허용해주세요.",
    [
      { text: "나중에", style: "cancel" },
      { text: "설정으로 이동", onPress: () => Linking.openSettings() },
    ]
  );
};

/**
 * 푸시 알림 권한 요청
 * @returns 권한 허용 여부 (true: 허용, false: 거부/취소)
 */
export const requestNotificationPermission = async (): Promise<boolean> => {
  // 1. 물리 디바이스 확인 (시뮤레이터는 푸시 알림 미지원)
  if (!Device.isDevice) {
    console.log("시뮤레이터에서는 푸시 알림이 지원되지 않습니다.");
    return true;  // 개발 편의를 위해 true 반환
  }

  // 2. Android 채널 설정
  await setupAndroidNotificationChannel();

  // 3. 현재 권한 상태 확인
  const { status: existingStatus } = await Notifications.getPermissionsAsync();

  // 4. 이미 허용됨
  if (existingStatus === "granted") {
    return true;
  }

  // 5. 미결정 상태 → 권한 요청 다이얼로그 표시
  if (existingStatus === "undetermined") {
    const { status } = await Notifications.requestPermissionsAsync();
    return status === "granted";
  }

  // 6. 거부됨 → 설정 앱 이동 안내
  showPermissionDeniedAlert();
  return false;
};

/**
 * 현재 알림 권한 상태 확인 (앱 어디서든 사용 가능)
 */
export const getNotificationPermissionStatus = async (): Promise<string> => {
  const { status } = await Notifications.getPermissionsAsync();
  return status;  // "granted" | "denied" | "undetermined"
};

컴포넌트에서 사용

// Step4AlarmScreen.tsx
import { requestNotificationPermission } from "../../../utils/notification";

const Step4AlarmScreen = () => {
  const handleAllowAlarm = async () => {
    const granted = await requestNotificationPermission();
    
    if (granted) {
      console.log("알림 권한 허용됨");
    } else {
      console.log("알림 권한 거부됨");
    }
    
    // 권한 허용 여부와 관계없이 다음 단계로 진행
    navigation.navigate("Step5Complete");
  };

  const handleSkip = () => {
    // 권한 요청 없이 다음 단계로
    navigation.navigate("Step5Complete");
  };

  return (
    <View>
      <Text>푸시 알림을 받아보세요</Text>
      <Button title="알람 허용" onPress={handleAllowAlarm} />
      <Button title="나중에 설정하기" onPress={handleSkip} />
    </View>
  );
};

권한 상태 종류

상태 설명 대응 방법
granted 이미 허용됨 바로 알림 사용 가능
undetermined 아직 결정 안함 (찫 요청) requestPermissionsAsync() 호출
denied 거부됨 설정 앱으로 안내 (Linking.openSettings())

iOS vs Android 차이점

항목 iOS Android
권한 필요 시점 항상 필요 13+ 버전부터 필요
알림 채널 불필요 8.0+ 버전 필수
시뮤레이터 푸시 알림 미지원 에뮤레이터에서 지원

Android 알림 채널 옵션

await Notifications.setNotificationChannelAsync("default", {
  name: "기본 알림",                              // 사용자에게 표시되는 이름
  importance: Notifications.AndroidImportance.MAX, // 중요도 (소리, 팝업 등)
  vibrationPattern: [0, 250, 250, 250],           // 진동 패턴
  lightColor: "#769fcd",                          // LED 색상
  sound: "default",                               // 알림 소리
});

importance 옵션

설명
MAX 소리 + 팝업 알림
HIGH 소리 + 알림
DEFAULT 소리
LOW 소리 없음
MIN 소리 없음 + 상태바에만 표시