스토어 배포 - Google Play, App Store
앱을 다 만들었다. 이제 flutter run이 아니라 사용자가 설치할 수 있는 파일을 만들어서 스토어에 올려야 한다. 가장 먼저 시도해 보는 건 이 명령이다.
flutter build appbundle빌드는 된다. 그런데 이 파일을 Google Play Console에 올리면 거부당한다.
You uploaded a debug APK/AAB... (또는)
You need to use a different package name because "com.example.myapp" is already used / not allowed.정확한 문구는 상황에 따라 다르지만 원인은 두 갈래다. 디버그용 서명으로 만들어진 파일이거나, 새 프로젝트의 기본 앱 ID인 com.example...이 그대로라서다. 스토어에 올리는 앱에는 내 앱만의 ID와 내 서명 키가 있어야 한다. 이 글은 그 준비를 순서대로 다룬다.
공통 준비: 올리기 전에 정할 것
- 앱 ID: Android의
applicationId와 iOS의 Bundle Identifier다.com.회사이름.앱이름처럼 전 세계에서 유일하게 짓고, 한 번 스토어에 올린 뒤에는 바꿀 수 없다 - 앱 이름, 아이콘, 스플래시: 이전 글에서 바꾼 것들이다
- 버전:
pubspec.yaml의version: 1.0.0+1이다.+앞은 사용자에게 보이는 버전 이름(1.0.0), 뒤는 스토어가 구분하는 빌드 번호(1)다 - 권한: 앱이 요구하는 권한(카메라, 위치 등)이 정말 필요한 것인지 점검한다
version: 1.0.0+1삽질 포인트: 같은 빌드 번호를 두 번 올릴 수 없다
스토어에 올린 빌드 번호는 이미 쓴 번호가 되고, 다음 업로드는 반드시 더 큰 번호여야 한다. Play Console은 "이미 사용된 버전 코드"라는 취지의 에러로 올리기를 거부한다. 올릴 때마다 +1, +2처럼 번호를 올리거나, 빌드 명령에서 지정한다.
flutter build appbundle --build-name=1.0.1 --build-number=2Android(Google Play) 1단계: 서명 키 만들기
Android는 키가 두 종류다.
- 업로드 키(upload key): 내가 앱을 올릴 때 쓰는 키다. 내가 만들고 내가 보관한다
- 앱 서명 키(app signing key): 사용자가 받는 앱에 서명하는 키다. Play App Signing을 쓰면 Google이 관리한다
Flutter 공식 문서가 Play App Signing을 쓰라고 안내하는 이유가 있다. 내가 앱 서명 키를 직접 들고 있다가 잃어버리면 그 앱은 업데이트를 못 하지만, Google이 관리하면 업로드 키를 잃어버려도 재설정을 요청할 수 있다. 업로드 키를 만든다.
keytool -genkey -v -keystore ~/upload-keystore.jks -keyalg RSA \
-keysize 2048 -validity 10000 -alias upload비밀번호와 이름 같은 정보를 물어본다. 만들어진 upload-keystore.jks 파일과 비밀번호는 절대 공개 저장소에 올리지 않고, 분실하지 않게 따로 백업한다.
2단계: 키를 프로젝트에 연결하기
android/key.properties 파일을 만든다.
storePassword=내가 정한 비밀번호
keyPassword=내가 정한 비밀번호
keyAlias=upload
storeFile=/Users/me/upload-keystore.jks이 파일은 비밀번호가 들어 있으니 .gitignore에 반드시 추가한다. .env와 .gitignore를 다룬 글과 같은 이유다. 그다음 android/app/build.gradle(.kts)에서 이 파일을 읽어 release 빌드에 쓰도록 연결한다.
// android/app/build.gradle.kts (코틀린 스크립트 예시)
val keystoreProperties = java.util.Properties()
val keystorePropertiesFile = rootProject.file("key.properties")
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(java.io.FileInputStream(keystorePropertiesFile))
}
android {
signingConfigs {
create("release") {
keyAlias = keystoreProperties["keyAlias"] as String
keyPassword = keystoreProperties["keyPassword"] as String
storeFile = file(keystoreProperties["storeFile"] as String)
storePassword = keystoreProperties["storePassword"] as String
}
}
buildTypes {
release {
signingConfig = signingConfigs.getByName("release")
}
}
}build.gradle.kts와 Groovy 형식의 build.gradle은 문법이 달라서, 프로젝트가 쓰는 쪽의 공식 문서 예시를 그대로 따라가는 게 안전하다. 새 프로젝트의 기본 release에는 "디버그 키로 서명한다"는 TODO 주석이 들어 있는데, 그 줄을 위 설정으로 바꾸는 작업이다. 이게 앞서 본 "디버그 서명" 에러의 원인이다.
3단계: 앱 번들 빌드
flutter build appbundle --release결과는 build/app/outputs/bundle/release/app.aab다. Google Play는 APK보다 AAB(앱 번들) 형식을 요구한다. 기기마다 필요한 부분만 내려받게 해서 사용자의 다운로드가 작아진다. 직접 설치용 APK가 필요하면 flutter build apk --split-per-abi로 CPU 종류별 APK를 만든다.
앱 코드를 난독화하려면 두 옵션을 같이 쓴다.
flutter build appbundle --obfuscate --split-debug-info=build/symbolsbuild/symbols 폴더는 나중에 크래시 로그를 읽을 때 필요하니 빌드마다 보관한다. 안드로이드의 R8(코드 축소)는 기본으로 켜져 있고, 문제가 생길 때는 --no-shrink로 끌 수 있다.
4단계: Play Console에 올리기
- Google Play Console에서 앱을 만든다(이름, 언어, 앱/게임 구분, 무료/유료)
- 스토어 등록정보를 채운다: 설명, 스크린샷, 아이콘, 소개 그래픽
- 앱 콘텐츠 항목을 채운다: 개인정보처리방침 URL, 데이터 보안, 콘텐츠 등급, 광고 포함 여부 등
- 테스트 트랙에 올린다: 내부 테스트(몇 명), 비공개 테스트, 공개 테스트 순으로 점점 범위를 넓힌다
- 검토를 거쳐 프로덕션으로 출시한다
처음 올릴 때는 .aab를 내부 테스트 트랙에 올려서 설치가 되는지부터 확인하는 게 안전하다. 개발자 계정의 종류와 가입 시기에 따라 프로덕션 출시 전에 비공개 테스트를 일정 인원과 기간 동안 거쳐야 하는 요건이 있을 수 있다. 이 인원과 기간, 그리고 앱이 대상으로 삼아야 하는 최소 Android API 수준은 정책이 바뀌므로 출시 전에 Play Console의 안내를 직접 확인한다.
iOS(App Store) 준비
iOS는 Mac과 Xcode가 필수이고, 유료 Apple Developer Program에 가입해야 한다.
- App Store Connect에 앱을 등록한다. 이때 쓰는 Bundle ID가 Xcode 프로젝트의 것과 같아야 한다
- Xcode에서
ios/Runner.xcworkspace를 열고 서명(Signing) 을 설정한다. 처음에는 "Automatically manage signing"을 켜고 팀을 고르면 인증서와 프로비저닝 프로파일을 Xcode가 만들어준다 - 버전과 빌드 번호를 확인한다.
pubspec.yaml의 값이 반영되고,--build-name,--build-number로 덮어쓸 수 있다
빌드와 업로드
flutter build ipa이 명령은 두 가지를 만든다. build/ios/archive/에 Xcode 아카이브(.xcarchive), build/ios/ipa/에 App Store용 .ipa 파일이다. 올리는 방법은 셋이다.
- Transporter 앱에
.ipa를 끌어다 놓기 - 터미널에서
xcrun altool --upload-app --type ios -f build/ios/ipa/*.ipa --apiKey ... --apiIssuer ...실행하기 - Xcode로
.xcarchive를 열어서 업로드하기
올라간 빌드는 App Store Connect의 TestFlight에서 먼저 시험해 본다. 문제가 없으면 App Store용 버전을 만들어서 스크린샷, 설명, 가격, 개인정보 항목을 채우고 심사에 제출한다. 심사를 통과하면 출시된다.
삽질 포인트: iOS 심사에서 자주 걸리는 것
- 권한 사용 이유 문구가 없다: 카메라, 사진, 위치 같은 권한을 쓰면
Info.plist에 왜 필요한지 설명하는 문구(예:NSCameraUsageDescription)가 반드시 있어야 한다. 빠지면 빌드 처리가 거부되거나 심사에서 반려된다 - 개인정보처리방침이 없다: 로그인이나 데이터 수집이 있다면 방침 URL이 필요하다
- 테스트 계정이 없다: 로그인이 있는 앱이라면 심사자가 쓸 테스트 계정을 심사 메모에 적어줘야 한다
- 빌드 번호가 같다: Android와 똑같이 올릴 때마다 번호가 커져야 한다
출시 전 체크리스트
- 앱 ID가
com.example이 아닌 내 ID인가 - 개발용 서버 주소가 아니라 운영 설정(flavor)으로 빌드했는가
- 서명 키(
key.properties,.jks)가 저장소에 올라가 있지 않고 백업되어 있는가 - 버전과 빌드 번호를 올렸는가
- 난독화를 썼다면
build/symbols를 보관했는가 - 실제 기기에서 release 빌드로 한 번 실행해 봤는가 (
flutter run --release)
마지막 항목이 특히 중요하다. 디버그에서는 되던 것이 release에서만 안 되는 경우가 있다. 예를 들어 안드로이드 release 빌드에서 INTERNET 권한이 빠져서 통신이 안 되는 경우다. HTTP 통신 글에서 본 그 함정이다.
Android Studio, Xcode와 비교하면
- 서명 키: Flutter는
key.properties와 Gradle 설정 / Android Studio는 Generate Signed Bundle 마법사가 같은 일을 함 / iOS는 Xcode의 Signing 설정 - 빌드 결과: Flutter
flutter build appbundle(.aab),flutter build ipa(.ipa) / 네이티브도 같은 형식의 파일 - 업로드: Play Console 웹 / App Store Connect와 Transporter, Xcode
Flutter 앱도 결국 일반 Android 앱, iOS 앱과 같은 형식의 파일이 만들어져 스토어에 올라간다. Kotlin이나 Swift 앱을 배포해 본 경험이 있다면 키, 번들 ID, 심사 과정은 그대로다. Flutter가 대신해 주는 건 flutter build 한 줄로 빌드 파일을 만드는 부분뿐이다.
정리
- 스토어에 올리려면
com.example...이 아닌 내 앱 ID, 디버그가 아닌 내 서명 키, 올릴 때마다 커지는 빌드 번호(pubspec.yaml의version: 1.0.0+1)가 필요하다 - Android는 업로드 키를
keytool로 만들어key.properties와 Gradle 설정에 연결하고(.gitignore필수), Play App Signing으로 앱 서명 키는 Google이 관리하게 한다 flutter build appbundle --release로.aab를 만들어 Play Console의 내부 테스트부터 올리고, 난독화하면build/symbols를 보관한다- 개인 개발자 계정의 비공개 테스트 요건과 최소 Android API 수준은 정책이 바뀌므로 출시 전에 Play Console 안내를 확인한다
- iOS는 Mac, Xcode, Apple Developer Program이 필요하고, App Store Connect에 앱을 등록하고 서명을 설정한 뒤
flutter build ipa로.ipa를 만들어 Transporter나xcrun altool로 올리고 TestFlight를 거쳐 심사에 제출한다 - iOS 심사는 권한 사용 이유 문구, 개인정보처리방침, 테스트 계정에서 자주 반려되고, 출시 전에는 release 빌드를 실제 기기에서 한 번 실행해 본다
다음 글에서는 빌드와 배포를 사람이 아니라 자동으로 하는 CI/CD(GitHub Actions, Codemagic)를 다뤄보겠다.
