최종 업데이트: 2026-09-16
![]()
COSMO Flutter 앱은 AAB 61MB 로 빌드돼 Google Play Console 에 올라갔고, 비공개 테스트에서 테스터 12명이 14일을 채웠고, 프로덕션 액세스를 신청했지만 “테스터가 설치만 하고 활동이 없다”는 사유로 반려됐어요. 같은 FastAPI 백엔드를 쓰는 앱인토스 미니앱(React)은 QR 테스트 한 번과 영업일 3일 검토로 2026년 8월 30일에 출시됐어요. 이 글은 dart-define 키 주입 빌드, 업로드 키 서명, AAB 생성 명령과 함께, 개인 개발자 계정(2023년 11월 13일 이후 생성)의 비공개 테스트 요건과 반려 후 다시 한다면 바꿀 6가지를 정리해요. 에뮬레이터 실측에서 잡은 세 번째 FCM 버그(요청 필드명 불일치 422)도 로그와 함께 적었어요.
먼저 정정이 있어요. 1편부터 3편까지 “웹(앱인토스)도 같은 Flutter 코드”라고 썼는데 틀렸어요. 미니앱은 React 로 따로 만든 앱이에요.
정정: COSMO 는 앱이 두 개이고 같은 것은 백엔드뿐이에요


왼쪽이 Flutter 앱, 오른쪽이 React 미니앱 홈이에요. 같은 데이터가 뜨지만 화면 코드는 완전히 다른 두 벌이고, 공유하는 것은 FastAPI 백엔드, PostgreSQL, 계정, 크롤러예요. 그래서 유지비가 두 배는 아니고 화면을 손볼 때만 두 번 일해요. 미니앱은 React 18 + Vite + 토스 TDS 로 만들었고 TypeScript 파일 27개예요.

미니앱을 처음 로컬(localhost:5173, Vite)에서 띄운 날 캡처예요. 아무말 목록이 불러와지지 않던 시점이고, Flutter 코드는 한 줄도 섞여 있지 않아요.
Flutter 앱 빌드·서명·AAB 명령은 무엇인가요?

# 1. 키 파일 (깃 제외, dart_defines.example.json 복사). 없으면 디버그에서 assert 로 바로 멈춤
# dart_defines.json
{
"KAKAO_NATIVE_APP_KEY": "<카카오 네이티브 앱 키>",
"GOOGLE_SERVER_CLIENT_ID": "<구글 OAuth 웹 클라이언트 ID>.apps.googleusercontent.com"
}
# 2. 개발 실행 / 디버그 APK (에뮬레이터·실기기 확인용, 187MB)
flutter run --dart-define-from-file=dart_defines.json
flutter build apk --debug --dart-define-from-file=dart_defines.json # Gradle 81초
# 3. 서명: android/key.properties + android/app/upload-keystore.jks (둘 다 깃 제외, RSA 2048)
storeFile=upload-keystore.jks
storePassword=<…>
keyAlias=upload
keyPassword=<…>
# 4. 스토어 업로드용 AAB (APK 아님). versionCode 는 pubspec 의 version: 1.0.0+10 에서 +10
flutter build appbundle --release --dart-define-from-file=dart_defines.json
# → build/app/outputs/bundle/release/app-release.aab (61MB)
# 5. 미니앱은 완전히 다른 명령 (tossapp/, React + Vite)
npm run build # → cosmo.ait (4.4MB, 한도 100MB)
npm run deploy # 앱인토스 콘솔 API 키 필요
| 항목 | 값 | 메모 |
|---|---|---|
| 버전 | 1.0.0+10 | +10 이 versionCode. 콘솔에 올린 횟수만큼 올라가요 |
| 서명 | upload-keystore.jks, RSA 2048 | key.properties 와 함께 git 제외. 잃어버리면 같은 앱으로 다시 올릴 수 없어요 |
| 업로드 형식 | AAB (app-release.aab) 61MB | Play Console 은 APK 가 아니라 AAB 만 받아요 |
| 키 주입 | –dart-define-from-file=dart_defines.json | 3편에서 걷어낸 하드코딩 대체. 없으면 디버그 assert 로 멈춤 |
| 디버그 APK | 187MB, Gradle 81초 | 에뮬레이터 확인용 |
| 미니앱 | npm run build → cosmo.ait 4.4MB | 한도 100MB. npm run deploy 는 앱인토스 콘솔 API 키 |
키스토어 하나만 강조할게요. Play 는 업로드 키로 서명한 AAB 만 같은 앱의 업데이트로 인정해요. 키스토어 파일과 비밀번호를 잃어버리면 새 패키지명으로 새 앱을 만들어야 해요. 파일은 git 밖 두 곳에, 비밀번호는 비밀번호 관리자에 두는 것을 권해요.
에뮬레이터 실측 결과는? 폰트는 됐고 FCM 은 한 번 더 고쳤어요

2편에서 찾은 Pretendard 미등록 버그를 고친 디버그 APK 를 에뮬레이터(Android API 36)에 올렸어요. 약관 동의 화면 글자가 시스템 폰트가 아니라 Pretendard 로 나오고, APK 안에 OTF 4개가 들어 있는 것도 확인했어요.
트러블슈팅: FCM 토큰 등록 422 (요청 필드명 불일치)

# 1차 실행 (앱은 {"token": ...}, 서버는 fcm_token 필드를 기다림)
I flutter : [FCM] Permission status: AuthorizationStatus.authorized
I flutter : [FCM] Token: <redacted>
I flutter : [FCM] not logged in yet, token kept for later # 로그인 전 보관 (의도대로)
# 테스트 로그인 직후
I flutter : [FCM] token register failed: DioException [bad response]: This exception was thrown because the response has a status code of 422
# 수정 한 줄 (lib/core/network/api_service.dart)
- data: {'token': token},
+ data: {'fcm_token': token}, // 백엔드 FcmTokenRequest.fcm_token 과 일치
# 2차 실행 (재빌드 후)
I flutter : [FCM] not logged in yet, token kept for later
I flutter : [FCM] token registered
- 앱 시작 → “[FCM] not logged in yet, token kept for later”: 로그인 전이라 보관. 의도대로예요.
- 테스트 로그인 → 보관한 토큰으로 서버 호출 → 422: 서버가 요청 본문을 거부했어요.
- 원인: 앱은 {“token”: …} 으로 보내고 FastAPI 의 FcmTokenRequest 는 fcm_token 필드를 기다리고 있었어요. 클로드코드가 양쪽을 다른 날 만들면서 이름이 어긋난 거예요.
- 한 줄 수정 후 재빌드 → “[FCM] token registered”.
3편에서 “양쪽 끝을 만들고 가운데를 잇지 않았다”고 썼는데, 잇고 나니 이름이 맞지 않았어요. 연결했다고 끝이 아니라 실제로 한 번 호출해봐야 해요. 에뮬레이터 로그 한 줄이 코드 리뷰 열 번보다 정확했어요. 에뮬레이터 팁 두 가지: 이전에 release 서명 빌드가 설치돼 있으면 debug APK 의 install -r 이 실패하니 먼저 uninstall 하고, 187MB 디버그 APK 는 저장공간 부족으로 설치가 막힐 수 있어요.
Google Play 개인 개발자 계정의 비공개 테스트 요건은 무엇인가요?

2023년 11월 13일 이후 만든 개인 개발자 계정은 비공개 테스트에서 테스터 12명이 14일 연속 옵트인해야 프로덕션 액세스를 신청할 수 있어요. 중간에 나갔다 들어온 테스터는 그 사람만 0일부터 다시 세요. 신청하면 설문 3파트(테스트에서 무엇을 했나 / 앱이 무엇인가 / 출시 준비가 됐나)를 쓰고, Google 이 최대 7일 심사해 메일로 알려줘요. 출처는 Play Console 고객센터: 앱 테스트 요구사항이에요.

2026년 5월 콘솔 대시보드예요. 비공개 테스트 트랙 1개 활성, “12명 이상 테스터로 14일 이상 진행” 항목만 남은 상태였어요.
| 날짜 | 한 것 | 결과 |
|---|---|---|
| 5/19 | 비공개 테스트 트랙에 versionCode 10 게시, 테스터 19명 초대 | 12명 옵트인 |
| 5/19 ~ 6/1 | 14일 유지 | 12명 이탈 없이 채움 |
| 6/1 | 프로덕션 액세스 신청 + 설문 | 심사 시작 |
| 6월 | Google 회신 | 반려. “테스터가 설치만 하고 활동이 없음” |
| 8/30 | 앱인토스 미니앱 출시 | 통과 (별도 트랙) |
숫자는 다 채웠는데 떨어진 거예요. 12명·14일은 최소 조건이고, 심사는 설문에 적은 테스터 활동과 실제 사용 데이터를 봐요.
비공개 테스트 반려 후 다시 한다면 무엇을 바꿀까요? 체크리스트 6개
테스터는 비공개 테스트 품앗이 카페나 오픈카톡 품앗이방에서 모았어요. “내 앱 테스트해주면 네 앱 테스트해줄게” 구조라 12명은 하루 이틀이면 모여요. 모으는 것은 문제가 아니었고 문제는 그 다음이에요.
- 설치만 시키지 마세요. “설치하고 14일 두세요”로 끝냈고, 반려 사유가 정확히 이거예요. 품앗이 테스터는 설치 후 앱을 열지 않는 경우가 많아요.
- 매일 할 일을 하나씩 주세요. “오늘은 검색해서 제품 하나 찜하기”처럼 1분짜리 과제를 카톡방에 매일 올리세요. 사용 로그가 남아야 해요.
- 피드백을 받아서 기록하세요. 설문 1파트가 “테스터 피드백 요약”이에요. 구글폼 하나로 3줄이라도 받아두면 설문에 쓸 것이 생겨요.
- Crashlytics·Analytics 를 켜두세요. 심사 기준은 비공개지만, “12명이 14일간 평균 N회 실행”을 설문에 숫자로 쓸 수 있어요.
- 테스터 14명 이상으로 시작하세요. 12명 딱 맞추면 한 명이 나가는 순간 다시 14일이에요. COSMO 는 19명 초대해 12명이 들어왔고 운 좋게 이탈이 없었어요.
- 설문은 길게, 구체적으로. “버그 3개 수정, 온보딩 흐름 변경”처럼 테스트로 무엇이 바뀌었는지 적어야 해요. “문제 없었음”이 가장 나쁜 답이에요.
반려되면 같은 트랙에서 다시 14일을 돌리고 재신청할 수 있어요. COSMO 는 그 사이 앱인토스로 먼저 출시했고, Play 는 위 6개를 적용해 다시 돌릴 예정이에요.
앱인토스는 왜 통과했나요? 관문이 달라요
앱인토스 콘솔은 cosmo.ait 를 올리고 QR 로 토스 앱에서 한 번 실행해보면 검토 요청 버튼이 열려요. 검토는 영업일 최대 3일이고 통과하면 바로 출시예요. 테스터 모집이 없어요. 대신 토스 웹뷰 안에서 돌아야 해서 네이티브 기능(카카오 SDK 로그인, 푸시)은 못 쓰고, 토스가 그리는 내비게이션 바 규칙을 따라야 해요. 그래서 React 로 따로 만든 거예요. 개인 개발자가 “일단 사용자를 받아보고 싶다”면 지금은 앱인토스 쪽 문이 훨씬 가까워요. 미니앱 쪽 이야기는 앱인토스 개발기 (3) 출시에 있어요.
스토어 등록 자료는 무엇을 만들어뒀나요?

- 아이콘 512×512, 그래픽 이미지 1024×500 (Play) / 로고 600×600, 프로모 1024×500 (앱인토스). 같은 꽃, 같은 핑크 #FF8FAB
- 스크린샷 1080×1920 7장 (Play) / 636×1048 5장 (앱인토스)
- 짧은 설명 40자, 전체 설명, 데이터 안전성 폼 답안, 개인정보처리방침 URL, 고객센터 이메일. 클로드코드에 “Play Console 입력값 전부 md 한 파일로”라고 시켜서 만들었어요
등록 자료는 두 스토어가 90% 겹쳐요. 한 파일에 정리해두면 두 번째 스토어는 30분이에요.
4부작을 마치며: 클로드코드로 앱을 만들 때 남는 것
- 구조 먼저(1편): 규칙 문서 → core → 모델 → 화면. 순서를 지키면 87개 파일이 같은 모양이에요.
- 디자인은 파일 4개(2편): 토큰·테마에 값을 가두면 화면 13개가 같은 얼굴이에요. 단, 폰트처럼 선언만 있고 번들되지 않은 것은 코드로는 보이지 않아요.
- 연결은 호출해봐야 안다(3편·4편): FCM 은 두 번 고쳤어요. 만든 것과 호출하는 것, 호출하는 것과 받는 것을 각각 확인해야 해요.
- 스토어는 코드 밖 문제(4편): 빌드가 되는 것과 출시되는 것은 다른 일이에요. 개인 계정이면 테스터 운영 계획을 코드보다 먼저 세우세요.
자주 묻는 질문 (FAQ)
Q. Google Play 비공개 테스트 테스터 12명은 어디서 구하나요?
비공개 테스트 품앗이 카페나 오픈카톡 품앗이방을 검색하면 돼요. 서로 앱을 설치해주는 구조라 하루 이틀이면 모여요. 다만 설치만 하는 테스터로는 심사를 넘지 못하니 매일 과제를 주고 피드백을 받으세요.
Q. 14일을 채웠는데 왜 반려되나요?
12명·14일은 신청 자격이지 통과 기준이 아니에요. 설문에 적는 테스터 활동·피드백·개선 내용과 실제 사용 데이터를 봐요. “설치만 하고 활동 없음”이 COSMO 의 반려 사유였어요.
Q. 반려되면 처음부터 다시 해야 하나요?
같은 비공개 테스트 트랙에서 다시 14일을 채우고 재신청하면 돼요. 앱을 새로 만들 필요는 없어요.
Q. Flutter 앱을 APK 로 Play 에 올리면 안 되나요?
Play Console 은 AAB 만 받아요. flutter build appbundle 로 만들고 서명은 key.properties 로 자동이에요.
Q. 앱인토스로 먼저 출시하는 것이 손해 아닌가요?
Play 출시가 막힌 동안 실제 사용자를 받을 수 있어서 이득이었어요. 백엔드가 같으니 미니앱 사용자가 쌓은 데이터가 Play 출시 후에도 그대로예요.
참고 자료
- Google Play Console 고객센터: 앱 테스트 요구사항 (개인 개발자 계정 테스터 12명·14일 연속·심사 7일)
- Flutter 공식 문서: Build and release an Android app (서명, AAB)
- Flutter 공식 문서: –dart-define-from-file
- 앱인토스 개발자 문서 (미니앱 콘솔·검토)
출처: COSMO 실제 빌드·콘솔 기록(2026년 5월~9월)과 2026년 9월 16일 에뮬레이터 실측. 키스토어 비밀번호, 키, 서버 주소, 테스터 모집 카페명은 글에 없어요.

Leave a Reply