안드로이드의 PUSH가 0건???

TEAM-PL 앱 개발 기록 — 푸시 알림 시스템 구축
프로젝트 개요
농구 동호회 관리 앱 (Expo React Native + Supabase)의 푸시 알림 시스템을 처음부터 구축하고,
Android FCM 설정 문제를 해결하며, 안정적인 서버 기반 알림 아키텍처로 전환한 과정.
1. Google Play 정식 출시
- 내부 테스트 → 공개 테스트 → 프로덕션 트랙 승격
- 기존 빌드(versionCode 42)를 "라이브러리에서 추가"로 재사용하여 재심사 없이 진행
- ProGuard 매핑 파일 경고는 Expo 관리형 빌드에서 정상 (무시 가능)
- iOS/Android 동시 출시 완료
2. 초대코드 버그 수정
- 문제: 사용자 입력을 toUpperCase() → toLowerCase()로 변환하는 과정에서 DB 저장값과 불일치하여
"Invalid code" 에러
- 해결: RPC 함수에 LOWER(invite_code) = LOWER(code) 적용, 대소문자 무관하게 매칭
3. 일정 탭 타임존 버그 수정
- 문제: new Date().toISOString()이 KST → UTC 변환하면서 월말 날짜가 하루 밀림 (3/31 → 3/30)
- 증상: 31일 경기가 일정 탭에서 안 보임 (홈에서는 보임)
- 해결: toISOString() 대신 직접 YYYY-MM-DD 문자열 생성으로 타임존 영향 제거
4. 경기 삭제 권한 버그 수정
- 문제: 부회장(co_captain)이 앱에서 경기 삭제 시 에러가 조용히 무시됨
- 원인 1: 모바일에서 에러 핸들링이 window.alert() (웹 전용)으로 되어있어 에러 메시지 미표시
- 원인 2: 삭제 확인 다이얼로그가 웹에서만 동작
- 해결: 모바일용 Alert.alert() 확인 다이얼로그 및 에러 핸들링 추가
5. 팀원 내보내기 시 데이터 정리 누락
- 문제: 게스트를 내보내기 했는데 attendance 레코드가 남아있어서 재참가 불가
- 원인: remove_team_member RPC에 attendance/join_requests 정리 로직 없음 (leave_team에만
있었음)
- 해결: remove_team_member에 leave_team과 동일한 정리 로직 추가 (attendance 삭제, join_requests
삭제)
6. 경기 생성 시 게스트 설정 미반영
- 문제: 팀 설정에서 게스트 모집을 켜놔도 크론으로 생성되는 경기에 guest_open: false,
guest_needed: 0으로 하드코딩
- 해결: 경기 생성 시 팀의 looking_for_guest, guest_count, guest_description 설정값을 자동 상속
7. 푸시 알림 시스템 설계 — 가장 큰 작업
DB 트리거 코드 예시 (게스트 참가)
CREATE OR REPLACE FUNCTION notify_on_guest_join()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.is_guest = false THEN RETURN NEW; END IF;
PERFORM net.http_post(
url := 'https://xxx.supabase.co/functions/v1/notify-team-leaders',
headers := jsonb_build_object(...),
body := jsonb_build_object(
'team_id', NEW.team_id,
'title', '게스트 참가 알림',
'body', NEW.player_name || '님이 게스트로 참가했습니다.'
)
);
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
CREATE TRIGGER trigger_notify_guest_join
AFTER INSERT ON team_members
FOR EACH ROW EXECUTE FUNCTION notify_on_guest_join();
시간 기반 알림 (5시간/1시간 전)
pg_cron (*/5 * * * *)
→ send-game-notifications Edge Function
→ game_sessions에서 start_time 기준 시간 윈도우 매칭
→ 5h 전: attendance(status='pending') → push_token 조회 → Expo Push API
→ 1h 전: 여전히 pending인 멤버만 → 재알림
8. Android FCM 설정 — 가장 삽질한 부분
증상
- iOS 알림 정상, Android 0건 발송
- Expo 대시보드: All Platforms 39, Android 0, iOS 39
원인
- iOS는 EAS Build 시 APNs 키가 자동 설정
- Android는 FCM 서비스 계정 키를 Expo 프로젝트에 수동 등록 필요
- androidAppCredentials.googleServiceAccountKeyForFcmV1이 null 상태
삽질 과정
1. ❌ Expo 대시보드 Push Notifications 페이지 — 업로드 UI 없음
2. ❌ Expo Credentials 페이지 — FCM 업로드 옵션 없음
3. ❌ eas credentials CLI — 업로드했지만 빌드 인증서에만 저장됨 (Push용 아님)
4. ❌ Google Cloud Console 레거시 API — 페이지 로드 에러
5. ❌ Expo REST API — 인증 차단 (403)
6. ✅ Expo GraphQL API 직접 호출로 해결!
해결 코드
# Step 1: 서비스 계정 키 생성
mutation {
googleServiceAccountKey {
createGoogleServiceAccountKey(
googleServiceAccountKeyInput: { jsonKey: $serviceAccountJson },
accountId: $accountId
) { id }
}
}
# Step 2: FCM V1에 연결 (이 mutation이 핵심!)
mutation {
androidAppCredentials {
setGoogleServiceAccountKeyForFcmV1(
id: $androidAppCredentialsId,
googleServiceAccountKeyId: $keyId
) { id }
}
}
GraphQL 스키마 탐색 방법
# AndroidAppCredentialsMutation에서 사용 가능한 필드 찾기
{ __type(name: "AndroidAppCredentialsMutation") {
fields { name args { name } }
} }
→ setGoogleServiceAccountKeyForFcmV1 발견!
9. pg_cron 장애 — 크론이 전부 실패하고 있었던 이유
증상
- 경기 자동 생성 안 됨, 5시간/1시간 전 알림 안 감
- 크론 로그: ERROR: unrecognized configuration parameter "app.settings.supabase_url"
원인
- 크론 명령에서 current_setting('app.settings.supabase_url') 참조
- 해당 DB 설정값이 어느 시점에 사라짐 (Supabase 인프라 변경 추정)
- ALTER DATABASE 로 재설정 시도 → 권한 부족 (permission denied)
해결
- current_setting() 대신 URL/키를 크론 명령에 직접 하드코딩
SELECT cron.schedule('create-game-sessions', '30 7 * * *', $$
SELECT net.http_post(
url := 'https://xxx.supabase.co/functions/v1/create-game-sessions',
headers := jsonb_build_object(
'Authorization', 'Bearer <service_role_key>',
'Content-Type', 'application/json'
),
body := '{}'::jsonb
);
$$);
10. 하네스(Harness) 구성 — AI 코딩 도구의 맥락 유실 방지
문제
- 새 대화마다 이전 맥락을 잃어 Edge Function이 잘못 수정되는 문제 반복
- "내일" 경기 생성이 "오늘"로 변경되거나, 알림 로직이 깨지는 등
해결
- 프로젝트 전용 에이전트 3개 정의 (supabase-engineer, app-engineer, deployer)
- 핵심 규칙을 에이전트에 명시: "create-game-sessions는 반드시 내일 경기 생성"
- 스킬 4개 (deploy, db-ops, notification-debug, orchestrator)
- Superpowers 플러그인 설치로 개발 프로세스 체계화
11. 배포 프로세스

체크!
1. Expo Android 푸시는 FCM 키 수동 등록 필수 — iOS와 달리 자동 안 됨,
eas credentials 빌드용과 푸시용이 다름
2. 클라이언트 알림 호출보다 DB 트리거가 안정적 — OTA 의존성 제거, 에러 추적 용이
3. toISOString()은 타임존 함정 — KST 환경에서 날짜 밀림 주의
4. pg_cron의 current_setting()은 사라질 수 있다 — 하드코딩이 더 안전
5. AI 코딩 도구는 맥락을 잃는다 — 하네스/메모리로 핵심 규칙 영속화 필수
6. 같은 폰에서 계정 전환 테스트는 부정확 — push_token이 마지막 로그인 기기로 덮어씌워짐
[pg_cron 16:30 KST]
→ create-game-sessions Edge Function
→ game_sessions INSERT
→ DB 트리거: 전체 멤버 알림 🔔
[pg_cron 매 5분]
→ send-game-notifications Edge Function
→ 5h 전: 미정 멤버 알림 🔔
→ 1h 전: 미정 멤버 재알림 🔔
[사용자 액션]
→ 게스트 참가 → team_members INSERT
→ DB 트리거: 회장/부회장 알림 🔔
→ 팀 가입 신청 → join_requests INSERT
→ DB 트리거: 회장/부회장 알림 🔔
작업요약
- 팀원 내보내기 데이터 정리 - attendance/join_requests 동시 삭제 (leave_team과 동일)
- 게스트 수락/거절 시스템
- 신규 게스트 승인제, 기존 게스트 자동 참가, 홈 액션 카드
- 홈 화면 액션 카드
- 가입 신청/게스트 신청을 홈에서 바로 수락/거절
- 게스트 모집 설정 가이드
- 팀 설정 미완료 시 경고 + 팀 설정 이동 안내
- 게스트 토글 가시성 개선
- OFF 시 진한 회색으로 구분 가능하게
- 게스트 참가 UI 개선
- "GUEST 입력" 힌트 → 바로 이동 버튼으로 변경
- 팀 생성 화면
- 사각형 로고 → 아이콘으로 변경
- 선수정보 출생년도 선택
- 나이 직접 입력 → 년생 선택 + 자동 나이 계산 (선수정보 수정 + 팀원정보 수정 모두)
- 하네스 구성 - 에이전트 3개 + 스킬 4개 + Superpowers 설치
- Firebase 서비스 계정 키 교체 - 노출된 키 삭제 + 새 키 등록
'역량 UP! > Business' 카테고리의 다른 글
| 6) SeMo(Settlement + Moa = 정산 모아) : 정산관리+대시보드+거래내역서 (0) | 2026.03.27 |
|---|---|
| 5) SeMo(Settlement + Moa = 정산 모아) : 발주, 송장, 상품, CS 관리 기능 추가 (0) | 2026.03.23 |
| 6) aw_project_🏀 팀플 App Store 정식 출시!! 🏀 (0) | 2026.03.16 |
| 4) SeMo(Settlement + Moa = 정산 모아) : 거래처 관련 기능 (0) | 2026.03.13 |
| 3) SeMo(Settlement + Moa = 정산 모아) : login 기능부터 시작!! (0) | 2026.03.11 |