blog
Expo + FCM으로 Android 푸시 알림 설정하기
Front-end / Mobile#Expo#React Native#FCM#Firebase#EAS#푸시 알림
Expo SDK 54 기준으로, WebView 기반 앱에서 Android 푸시 알림을 붙인 과정이다.
- 전송 경로: 서버 → Expo Push Service → FCM HTTP v1 → Android 기기
- 패키지:
expo-notifications,expo-device,expo-constants
웹(WebView)
└─ bridge.requestPushPermission()
└─ requestPushNotificationPermission()
└─ Expo Push Token 발급 → 서버에 저장
└─ 서버가 Expo Push API 호출 → FCM v1 → 기기
초기 설정
1. Firebase 프로젝트에 Android 앱 추가
- Firebase Console에서 프로젝트를 만들거나 선택한다.
- 프로젝트 설정 → 일반 → Android 앱 추가. 패키지 이름은
app.config.js의android.package와 같아야 한다(예:com.example.app). google-services.json을 내려받는다.
2. google-services.json을 EAS 환경 변수로 관리
이 파일은 .gitignore에 넣고, EAS에 파일 타입 환경 변수로 등록한다.
eas env:create
# name: GOOGLE_SERVICES_JSON
# type: file
# visibility: secret
# environment: development (필요한 환경 선택)
EAS Build는 Git에 커밋된 파일만 업로드하므로, 커밋하지 않은 google-services.json을 경로로 지정하면 빌드에서 찾지 못한다. 파일 타입 환경 변수는 빌드 서버에 파일을 만들고 그 경로를 변수에 넣어 준다.
// app.config.js
export default {
expo: {
android: {
package: 'com.example.app',
googleServicesFile: process.env.GOOGLE_SERVICES_JSON ?? './google-services.json',
},
},
};
app.json은 정적 JSON이라 process.env를 읽을 수 없다. 환경 변수를 쓰려면 app.config.js(또는 .ts)로 바꿔야 한다.
3. FCM v1 서비스 계정 키 등록
Expo 서버가 FCM으로 알림을 보내려면 Firebase 서비스 계정 키가 필요하다.
- Firebase Console → 프로젝트 설정 → 서비스 계정 → 새 비공개 키 생성
- 키를 EAS에 등록한다.
eas credentials
# Android → Google Service Account
# → Manage your Google Service Account Key for Push Notifications (FCM V1)
# → Set up ... → 내려받은 JSON 경로 입력
이 키는 프로젝트에 대한 관리자 권한을 가지므로 저장소에 커밋하지 않는다.
4. 개발 빌드
푸시 알림은 Expo Go가 아니라 개발 빌드(development build) 에서 테스트한다.
eas build --profile development --platform android
권한 요청과 토큰 발급
import Constants from 'expo-constants';
import * as Device from 'expo-device';
import * as Notifications from 'expo-notifications';
import { Platform } from 'react-native';
export async function requestPushNotificationPermission() {
if (!Device.isDevice) throw new Error('실제 기기에서만 푸시를 사용할 수 있습니다.');
// Android 13+는 채널이 있어야 권한 요청 팝업이 뜬다
if (Platform.OS === 'android') {
await Notifications.setNotificationChannelAsync('default', {
name: 'default',
importance: Notifications.AndroidImportance.MAX,
});
}
const { status: existing } = await Notifications.getPermissionsAsync();
const { status } =
existing === 'granted' ? { status: existing } : await Notifications.requestPermissionsAsync();
if (status !== 'granted') throw new Error('알림 권한이 거부되었습니다.');
const projectId = Constants.expoConfig?.extra?.eas?.projectId;
const { data: token } = await Notifications.getExpoPushTokenAsync({ projectId });
return token; // ExponentPushToken[...]
}
웹뷰 브릿지에서는 성공 시 { success: true, token }, 실패 시 { success: false, error } 형태로 웹에 결과를 돌려주었다.
전송 테스트
curl -X POST https://exp.host/--/api/v2/push/send \
-H "Content-Type: application/json" \
-d '{
"to": "ExponentPushToken[YOUR_TOKEN_HERE]",
"title": "테스트 알림",
"body": "알림이 잘 오나요?"
}'
응답의 {"data": {"status": "ok", "id": "..."}}는 Expo가 요청을 접수했다는 뜻이다. 실제 전달 결과는 이 id로 push receipt API를 조회해서 확인한다(FCM 자격 증명 오류도 receipt에서 드러난다).
Firebase 프로젝트를 바꿀 때
- 새
google-services.json으로 EAS 환경 변수GOOGLE_SERVICES_JSON값을 갱신한다(eas env:update). eas credentials에서 FCM v1 서비스 계정 키를 새 프로젝트의 키로 교체한다.- 다시 빌드한다.
google-services.json은 앱 바이너리에 포함되므로 재빌드 없이는 바뀌지 않는다.
트러블슈팅
| 에러 | 원인 | 해결 |
|---|---|---|
Default FirebaseApp is not initialized | 빌드에 google-services.json이 포함되지 않음 | 환경 변수 등록 후 재빌드 |
open '$GOOGLE_SERVICES_JSON' | app.json에서 환경 변수 치환 시도 | app.config.js로 전환 |
google-services.json is missing (EAS Build) | Git에 없는 파일을 경로로 지정 | 파일 타입 EAS 환경 변수 사용 |
Unable to retrieve the FCM server key (receipt) | FCM v1 서비스 계정 키 미등록 | eas credentials에서 등록 |