Flutter를 사용하여 iOS/Android 앱을 동시에 만드는 과정을 기록하기 위해 글을 정리했습니다. 클로드를 사용하여 초안을 작성후 경험을 바탕으로 수정했습니다. 개선안이나 문제점이 있으면 알려주세요.
.
2026. 4. 28 최초작성
.
.
.
Flutter 앱 iOS/Android 동시 빌드 및 스토어 출시 가이드
Flutter를 사용하여 앱을 개발하면 하나의 코드로 iOS와 Android를 동시에 개발할 수 있습니다. 다만 각 플랫폼마다 서명·배포 과정이 다르기 때문에, 출시를 준비하면서 헷갈리는 부분이 많았습니다.
.
여기 나온 부분의 대부분은 클로드 코드에게 Flutter를 사용하여 iOS와 Android 앱을 동시에 만들어달라고하면 생성해주지만 알고 있어야 할거 같습니다.
프로젝트 폴더에 ios와 android 폴더가 각각 생성됩니다.
.
.
.
처음 앱을 생성했을때 한 번만 해두면 되는 최초 설정과 매 빌드/출시마다 반복하는 작업을 구분해서 정리해두고 필요할때 마다 참고하려고 합니다.
.
[최초 1회]로 표시된 섹션은 프로젝트 최초 세팅했을때만 수행합니다.
[매번 반복]으로 표시된 섹션은 빌드/출시 할때 반복해서 수행합니다.
.
.
1. [최초 1회] 프로젝트 초기 설정
1-1. Android 키스토어 생성
Play Store에 업로드하려면 AAB에 디지털 서명이 필요합니다. 최초 1회 키스토어를 만들어둡니다.
keytool -genkey -v -keystore ~/upload-keystore.jks \
-keyalg RSA -keysize 2048 -validity 10000 -alias upload
.
실행 시 이름, 조직, 지역 등을 묻습니다. 예시:
유효 기간 10,000일, 2,048비트 RSA 키 쌍 및 자체 서명된 인증서(SHA256withRSA)
: CN=이정주, OU=Unknown, O=Unknown, L=영등포구, ST=서울시, C=kr
[/Users/webnautes/upload-keystore.jks 저장 완료]
.
매우 중요: upload-keystore.jks 파일과 비밀번호는 절대 분실하면 안 됩니다. 분실 시 앱 업데이트가 영구적으로 불가능합니다. 클라우드·USB 등 안전한 곳에 반드시 백업하세요.
keytool 명령이 없다면 flutter doctor -v로 “Java binary at:” 경로를 찾은 뒤 java 대신 keytool로 바꿔 실행하면 됩니다.
.
jks 파일에 대한 비밀번호 인증은 다음 명령으로 할 수 있습니다. 비밀번호 한 번만 물어보고, 맞으면 인증서 정보가 다 출력됩니다.
keytool -list -v -keystore upload-keystore.jks
.
.
1-2. key.properties 파일 생성
위치: [프로젝트루트]/android/key.properties
storePassword=실제_비밀번호
keyPassword=실제_비밀번호
keyAlias=upload
storeFile=/Users/webnautes/upload-keystore.jks
평문으로 jks에 대한 비밀번호를 입력해놓는게 Flutter 공식 방식이라는게 이상하네요
이 파일은 반드시 .gitignore에 추가해서 Git 원격 저장소에 올라가지 않도록 해야 합니다. storeFile은 절대경로로 적는 것이 가장 안전합니다.
.
.
1-3. Android Gradle 서명 설정
필수 단계입니다. 안 하면 Play 업로드 자체가 안 됩니다.
.
먼저 본인 프로젝트가 어떤 Gradle 파일을 쓰는지 확인합니다.
ls android/app/build.gradle*
- build.gradle.kts가 있으면 → Kotlin DSL (최신 Flutter 기본값)
- build.gradle만 있으면 → Groovy (기존 방식)
아래 작업은 기존 파일을 전체 교체하는 것이 아니라, 필요한 부분만 추가/수정합니다. 파일 전체를 지우고 아래 코드로 덮어쓰면 안 됩니다.
.
Case A: android/app/build.gradle.kts (Kotlin DSL)
① 파일 최상단(plugins { } 블록 바로 다음, android { } 블록 시작 전)에 keystoreProperties 로딩 코드를 추가합니다.
import java.util.Properties
import java.io.FileInputStream
// 기존 plugins { … } 블록 유지
val keystoreProperties = Properties()
val keystorePropertiesFile = rootProject.file(“key.properties”)
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
}
android {
// …기존 내용 그대로 둠
}
② 기존 android { } 블록 안에 signingConfigs를 추가합니다. (이미 signingConfigs 블록이 있다면 그 안에 create(“release”) { }만 추가)
android {
// …기존 설정
signingConfigs {
create(“release”) {
keyAlias = keystoreProperties[“keyAlias”] as String
keyPassword = keystoreProperties[“keyPassword”] as String
storeFile = keystoreProperties[“storeFile”]?.let { file(it) }
storePassword = keystoreProperties[“storePassword”] as String
}
}
buildTypes {
release {
// 기존에 `signingConfig = signingConfigs.getByName(“debug”)` 같은 줄이
// 있으면 아래 줄로 교체합니다.
signingConfig = signingConfigs.getByName(“release”)
}
}
}
buildTypes { release { … } }에 원래 signingConfigs.getByName(“debug”)로 되어 있는 경우가 많습니다. 이 줄을 반드시 signingConfigs.getByName(“release”)로 바꿔야 실제 릴리즈 키로 서명됩니다.
.
Case B: android/app/build.gradle (Groovy)
① 파일 최상단 (android { } 블록 위)에 추가:
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file(‘key.properties’)
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
② 기존 android { } 블록 안에 추가:
android {
// …기존 설정
signingConfigs {
release {
keyAlias keystoreProperties[‘keyAlias’]
keyPassword keystoreProperties[‘keyPassword’]
storeFile keystoreProperties[‘storeFile’] ? file(keystoreProperties[‘storeFile’]) : null
storePassword keystoreProperties[‘storePassword’]
}
}
buildTypes {
release {
// 기존 `signingConfig signingConfigs.debug` 줄을 아래로 교체
signingConfig signingConfigs.release
}
}
}
.
.
1-4. iOS Xcode 서명 설정
Xcode에서 프로젝트를 열기 전에, iOS 관련 파일들(Pods/, Generated.xcconfig 등)이 생성되어 있어야 합니다. 프로젝트를 처음 받은 상태라면 먼저 실행합니다.
flutter pub get
cd ios
pod install
cd ..
.
.
그다음 Xcode로 ios 폴더를 엽니다.
open ios
Xcode가 열리면:
- 좌측 파일 네비게이터(가장 왼쪽 패널) 맨 위에서 파란색 Runner 아이콘(프로젝트 파일) 클릭
- 중앙 편집 영역 왼쪽에 PROJECT와 TARGETS 목록이 나타남
- TARGETS 아래의 Runner 선택
- 상단 탭에서 Signing & Capabilities 선택
.
여기서 다음 항목을 설정합니다:
- Team: 본인 Apple Developer 계정 팀 선택 (드롭다운)
- Bundle Identifier: App Store Connect에 등록한 번들 ID와 동일하게 설정
- Automatically manage signing: 체크 (대부분의 경우 권장)
.
1-5. iOS 지원 기기 설정 (필요 시)
iPhone만 지원할 경우(iPad 심사 이슈를 피하려면):
- TARGETS → Runner 선택 (위와 동일한 경로)
- 상단 탭에서 General 선택
- Supported Destinations 섹션에서 iPad 행을 선택한 뒤, 해당 행 좌측(또는 우측)의 빼기(−) 버튼 클릭
- iPhone만 남아 있는지 확인
.
Xcode에서만 아이폰만을 위한 앱을 만든다고 설정하면 안되고
클로드 코드에게 아이폰만을 위한 설정을 Flutter에도 해달라고 해야 했습니다.
.
.
2. [매번 반복] 개발 중 실기기 테스트
2-1. Android — 터미널에서 실행
연결된 기기 확인:
flutter devices
.
디버그 모드로 실행:
flutter run
.
릴리즈 모드로 설치 (테스트용):
.
# 기존 앱 제거 후 설치
flutter build apk –release && flutter install –release
.
# 기존 앱에 덮어쓰기 (데이터 유지)
flutter build apk –release && \
~/Library/Android/sdk/platform-tools/adb install -r \
build/app/outputs/flutter-apk/app-release.apk
.
Android 실기기 연결 시 기기의 개발자 옵션 → USB 디버깅이 켜져 있어야 하고, 첫 연결 시 “이 컴퓨터에서 USB 디버깅 허용” 팝업을 허용해야 합니다.
.
.
2-2. iOS — Xcode에서 실행
ios 폴더를 Xcode로 엽니다.
open ios
프로젝트를 새 환경에서 처음 열거나 빌드 에러가 날 경우, 먼저 3-2. 캐시 정리 단계를 실행해서 Pods/를 재설치한 뒤 Xcode를 열어주세요.
.
.
디버그 모드로 설치
- 메뉴: Product → Scheme → Edit Scheme… (또는 ⌘<)
- Scheme 편집 창이 열리면, 좌측 목록(Run, Test, Profile, Analyze, Archive 중)에서 Run 선택
- 상단 탭에서 Info 선택 (Arguments, Options, Diagnostics 등과 나란히 있음)
- Build Configuration 드롭다운: Debug
- 그 아래 Executable 항목 옆의 Debug executable 체크박스: ✅ 체크
- 우측 하단 Close
- Xcode 상단 기기 선택 드롭다운에서 연결된 실제 iPhone 선택
- 좌측 상단 ▶ (Run) 버튼 클릭 (또는 ⌘R)
.
.
릴리즈 모드로 설치
- 메뉴: Product → Scheme → Edit Scheme… (또는 ⌘<)
- 좌측 목록에서 Run 선택 → 상단 Info 탭
- Build Configuration 드롭다운: Release로 변경
- Debug executable 체크박스: ❌ 체크 해제
- Close
- 상단 기기에서 실제 iPhone 선택 후 ▶ (Run) 클릭
테스트가 끝나면 Scheme 설정을 Debug + Debug executable 체크로 다시 되돌려두는 것이 편합니다. 그래야 이후 개발 중에 브레이크포인트와 핫 리로드가 정상 작동하고 빌드도 훨씬 빠릅니다.
iOS 실기기 연결 시 기기의 설정 → 일반 → VPN 및 기기 관리에서 개발자 인증서를 신뢰해야 설치된 앱이 실행됩니다.
.
.
3. [매번 반복] 출시 전 준비
3-1. 버전 업데이트 (pubspec.yaml)
Flutter는 pubspec.yaml의 version 한 줄로 iOS와 Android 버전을 모두 관리합니다.
version: 1.0.1+2
- 1.0.1 → 사용자에게 보이는 버전
- Android: versionName
- iOS: CFBundleShortVersionString
- +2 → 내부 빌드 번호 (업로드마다 반드시 증가)
- Android: versionCode
- iOS: CFBundleVersion
심사 리젝 후 재업로드할 때도 빌드 번호(+ 뒤 숫자)는 반드시 올려야 합니다. 같은 번호로는 스토어에 재업로드할 수 없습니다.
.
.
3-2. 캐시 정리
flutter clean
flutter pub get
.
.
iOS도 함께 빌드한다면, 이어서 다음 명령도 실행합니다. flutter clean은 Flutter/Dart 캐시만 정리하고 iOS 네이티브 의존성(CocoaPods)은 건드리지 않기 때문입니다.
cd ios
rm -rf Pods Podfile.lock
pod install
cd ..
.
.
4. [매번 반복] Android 출시
4-1. App Bundle 빌드
flutter build appbundle –release
빌드가 성공하면 다음 위치에 AAB 파일이 생성됩니다.
build/app/outputs/bundle/release/app-release.aab
플레이스토어에 업로드시 이 파일을 사용하면 됩니다.
.
.
5. [매번 반복] iOS 출시
5-1. Xcode로 프로젝트 열기
open ios
5-2. 빌드 타겟 설정
- 상단 기기 선택 드롭다운: Any iOS Device (arm64)
.
.
5-3. Archive 생성
메뉴: Product → Archive
빌드가 시작되고, 완료되면 Organizer 창이 자동으로 열립니다. (수동으로 열려면 Window → Organizer)
.
.
5-4. Distribute App (업로드)
1. 버전 확인
Organizer 창에서 Version/Build 번호가 pubspec.yaml과 일치하는지 확인. 안 맞으면
.
flutter clean && flutter pub get
cd ios && rm -rf Pods Podfile.lock && pod install && cd ..
.
Xcode의 메뉴에서 Product > Clean Build Folder를 선택한 후, 다시 Archive를 실행합니다.
.
.
2. Distribute App 진행
Distribute App → 다음 옵션 선택:
| 단계 | 선택 |
| 배포 방식 | Custom |
| 대상 | App Store Connect |
| 동작 | Upload (Export 아님) |
| 옵션 | “Manage Version and Build Number” 체크 해제 ⚠️ |
| 서명 | Automatically manage signing |
.
.
3. Upload 클릭
Xcode가 자동 검증 후 App Store Connect로 전송. App Store Connect 앱 상세 페이지의 Activities 탭에서 빌드 상태를 확인할 수 있고, 기다리면 빌드 검증 완료 이메일이 도착합니다.
.
.