[Flutter + Firebase로 북마크 앱 만들기 #6] Android 앱 빌드 및 Play Store 배포 준비

도경원's avatar
Oct 11, 2025
[Flutter + Firebase로 북마크 앱 만들기 #6] Android 앱 빌드 및 Play Store 배포 준비

📌 시리즈 목차


🎯 이번 글에서 다룬 내용

  • Android 앱 빌드 환경 설정
  • 앱 아이콘 및 메타데이터 설정
  • Keystore 생성 및 앱 서명
  • Release 빌드 (APK/AAB)
  • Firebase 설정 업데이트
  • 트러블슈팅 (실전 경험담)

📱 목표: Play Store 배포 준비 완료

최종 산출물:
  • ✅ Release APK (테스트용)
  • ✅ Release AAB (Play Store 업로드용)
  • ✅ 서명된 빌드
  • ✅ 에뮬레이터 테스트 완료

1️⃣ Android 빌드 준비

앱 기본 정보 결정

앱 이름

LinkOn

Application ID (패키지명)

com.doythan.bookmarkmanager
중요:
  • 한 번 정하면 변경 불가능!
  • com.example은 Play Store 업로드 불가
  • 형식: com.[개발자명].[앱명]

버전 정보

versionCode: 1 versionName: 1.0.0

2️⃣ 앱 아이콘 설정

flutter_launcher_icons 패키지 설치

flutter pub add --dev flutter_launcher_icons

아이콘 파일 준비

# 폴더 생성 mkdir -p assets/icon # 512x512 PNG 아이콘 준비 # (Figma, Canva, AI 도구 등으로 제작)

pubspec.yaml 설정

위치: pubspec.yaml
# 파일 끝에 추가 flutter_launcher_icons: android: true ios: true image_path: "assets/icon/icon.png" min_sdk_android: 21 adaptive_icon_background: "#2196F3" adaptive_icon_foreground: "assets/icon/icon.png"

아이콘 생성

flutter pub get flutter pub run flutter_launcher_icons
결과:
✓ Creating default icons Android ✓ Creating default icons iOS ✓ Creating adaptive icons Android

3️⃣ 앱 메타데이터 설정

AndroidManifest.xml 수정

위치: android/app/src/main/AndroidManifest.xml
<manifest ...> <application android:label="LinkOn" android:name="${applicationName}" android:icon="@mipmap/ic_launcher">
변경사항:
  • android:label: 앱 이름

build.gradle 수정

위치: android/app/build.gradle

파일 확인

ls android/app/build.gradle*
주의: .kts 파일이면 .gradle로 변경 필요!
# Kotlin DSL을 Groovy로 변경 mv android/app/build.gradle.kts android/app/build.gradle

build.gradle 설정

plugins { id("com.android.application") id("com.google.gms.google-services") id("kotlin-android") id("dev.flutter.flutter-gradle-plugin") } android { namespace = "com.doythan.bookmarkmanager" compileSdk = flutter.compileSdkVersion ndkVersion = flutter.ndkVersion compileOptions { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 } kotlinOptions { jvmTarget = JavaVersion.VERSION_11.toString() } defaultConfig { applicationId = "com.doythan.bookmarkmanager" // ✅ 패키지명 minSdk = flutter.minSdkVersion targetSdk = flutter.targetSdkVersion versionCode = flutter.versionCode versionName = flutter.versionName } buildTypes { release { signingConfig = signingConfigs.getByName("debug") } } } flutter { source = "../.." }
핵심 변경:
  • applicationId: com.doythan.bookmarkmanager로 변경
  • com.example 제거!

4️⃣ Keystore 생성 및 앱 서명

Keystore란?

앱 서명 키: Play Store에 업로드하는 모든 APK/AAB는 동일한 키로 서명되어야 함
중요:
  • 키를 잃어버리면 앱 업데이트 영구 불가능!
  • 반드시 안전한 곳에 백업!

Keystore 생성

keytool -genkey -v -keystore ~/upload-keystore.jks \ -keyalg RSA -keysize 2048 -validity 10000 \ -alias upload \ -storetype JKS
입력 프롬프트:
키 저장소 비밀번호를 입력하십시오: [강력한 비밀번호] 새 비밀번호를 다시 입력하십시오: [비밀번호 재입력] 이름과 성을 입력하십시오. [알 수 없음]: 경원도 조직 단위 이름을 입력하십시오. [알 수 없음]: 개인 조직 이름을 입력하십시오. [알 수 없음]: Doythan 구/군/시 이름을 입력하십시오? [알 수 없음]: Busan 시/도 이름을 입력하십시오. [알 수 없음]: Busan 이 조직의 두 자리 국가 코드를 입력하십시오. [알 수 없음]: KR
생성 확인:
ls -la ~/upload-keystore.jks

key.properties 파일 생성

위치: android/key.properties
storePassword=[입력한 비밀번호] keyPassword=[입력한 비밀번호] keyAlias=upload storeFile=/Users/dogyeong-won/upload-keystore.jks
⚠️ 보안 주의:
  • 절대 Git에 커밋하지 말 것!
  • .gitignore에 추가 필수!

.gitignore 업데이트

echo "android/key.properties" >> .gitignore
확인:
git status # key.properties가 안 보여야 정상!

build.gradle에 서명 설정 추가

위치: android/app/build.gradle
전체 파일:
plugins { id("com.android.application") id("com.google.gms.google-services") id("kotlin-android") id("dev.flutter.flutter-gradle-plugin") } def keystoreProperties = new Properties() def keystorePropertiesFile = rootProject.file('key.properties') if (keystorePropertiesFile.exists()) { keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) } android { namespace = "com.doythan.bookmarkmanager" compileSdk = flutter.compileSdkVersion ndkVersion = flutter.ndkVersion compileOptions { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 } kotlinOptions { jvmTarget = JavaVersion.VERSION_11.toString() } defaultConfig { applicationId = "com.doythan.bookmarkmanager" minSdk = flutter.minSdkVersion targetSdk = flutter.targetSdkVersion versionCode = flutter.versionCode versionName = flutter.versionName } signingConfigs { release { keyAlias keystoreProperties['keyAlias'] keyPassword keystoreProperties['keyPassword'] storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null storePassword keystoreProperties['storePassword'] } } buildTypes { release { signingConfig = signingConfigs.release minifyEnabled true shrinkResources true } } } flutter { source = "../.." }
추가된 부분:
  • def keystoreProperties: key.properties 로드
  • signingConfigs: 서명 설정
  • buildTypes.release: Release 빌드 시 서명 적용
  • minifyEnabled: 코드 난독화
  • shrinkResources: 사용하지 않는 리소스 제거

5️⃣ Firebase 설정 업데이트

문제 상황

패키지명 변경 후 Firebase와 불일치:
Firebase: com.example.bookmark_manager 현재 앱: com.doythan.bookmarkmanager
에러:
No matching client found for package name 'com.doythan.bookmarkmanager'

해결: Firebase Console에서 Android 앱 추가

1. Firebase Console 접속

https://console.firebase.google.com/project/bookmark-manager-24e55/settings/general

2. Android 앱 추가

1. "앱 추가" 버튼 클릭 2. Android 아이콘 선택 3. Android 패키지 이름: com.doythan.bookmarkmanager 4. 앱 닉네임: LinkOn (선택) 5. 디버그 서명 인증서 SHA-1: (나중에 추가 가능) 6. "앱 등록" 클릭

3. google-services.json 다운로드

1. "google-services.json 다운로드" 버튼 2. 다운로드된 파일을 프로젝트에 복사
cp ~/Downloads/google-services.json android/app/

4. 확인

cat android/app/google-services.json | grep package_name
결과:
"package_name": "com.doythan.bookmarkmanager"

6️⃣ MainActivity 패키지 구조 정리

문제 발견

에러 상황: Debug 모드 실행 시 앱이 크래시
원인:
  • 패키지 ID는 com.doythan.bookmarkmanager로 변경
  • 하지만 폴더 구조는 여전히 com.example.bookmark_manager
폴더 불일치:
현재: android/app/src/main/kotlin/com/example/bookmark_manager/ 필요: android/app/src/main/kotlin/com/doythan/bookmarkmanager/

해결: 새 패키지 구조 생성

1. 새 폴더 생성

mkdir -p android/app/src/main/kotlin/com/doythan/bookmarkmanager

2. MainActivity.kt 생성

위치: android/app/src/main/kotlin/com/doythan/bookmarkmanager/MainActivity.kt
package com.doythan.bookmarkmanager import io.flutter.embedding.android.FlutterActivity class MainActivity: FlutterActivity() { }

3. 기존 폴더 삭제

rm -rf android/app/src/main/kotlin/com/example

최종 폴더 구조

android/app/src/main/kotlin/ └── com/ └── doythan/ └── bookmarkmanager/ └── MainActivity.kt

7️⃣ 빌드 및 테스트

Clean 빌드

flutter clean flutter pub get

Debug 빌드 테스트

flutter run
성공:
✓ Built build/app/outputs/flutter-apk/app-debug.apk Installing build/app/outputs/flutter-apk/app-debug.apk... Waiting for sdk gphone64 arm64 to report its views... Debug service listening on ws://127.0.0.1:xxxxx
앱 실행 확인:
  • ✅ 로그인 화면 표시
  • ✅ 회원가입 가능
  • ✅ 북마크 CRUD 동작
  • ✅ 검색 동작

Release APK 빌드

flutter build apk --release
결과:
Font asset "CupertinoIcons.ttf" was tree-shaken, reducing it from 257628 to 848 bytes (99.7% reduction). Font asset "MaterialIcons-Regular.otf" was tree-shaken, reducing it from 1645184 to 3508 bytes (99.8% reduction). ✓ Built build/app/outputs/flutter-apk/app-release.apk (50.1MB)
Tree-shaking:
  • 사용하지 않는 아이콘 제거
  • 99.7-99.8% 크기 감소!

Release AAB 빌드 (Play Store용)

flutter build appbundle --release
결과:
✓ Built build/app/outputs/bundle/release/app-release.aab (44.4MB)
APK vs AAB:
APK: 50.1MB (직접 설치용) AAB: 44.4MB (Play Store 업로드용, 더 작음)

빌드 파일 확인

# APK ls -lh build/app/outputs/flutter-apk/app-release.apk # AAB ls -lh build/app/outputs/bundle/release/app-release.aab

Release APK 에뮬레이터 테스트

# 기존 앱 삭제 adb uninstall com.doythan.bookmarkmanager # Release APK 설치 adb install build/app/outputs/flutter-apk/app-release.apk # 앱 실행 adb shell am start -n com.doythan.bookmarkmanager/.MainActivity
테스트 항목:
✅ 앱 아이콘 정상 표시 ✅ 앱 이름 "LinkOn" 표시 ✅ 모든 기능 정상 동작 ✅ 성능 문제 없음 ✅ 크래시 없음

8️⃣ 트러블슈팅

문제 1: Kotlin DSL vs Groovy 에러

에러:
Script compilation errors: Line 01: def keystoreProperties = new Properties() ^ Unresolved reference: def
원인:
  • 파일명이 build.gradle.kts (Kotlin DSL)
  • 하지만 코드는 Groovy 문법
해결:
mv android/app/build.gradle.kts android/app/build.gradle

문제 2: plugins 블록 순서

에러:
only buildscript {}, pluginManagement {} and other plugins {} script blocks are allowed before plugins {} blocks
원인: plugins {} 블록이 파일 맨 위에 와야 함
해결: plugins {} 블록을 파일 최상단으로 이동
올바른 순서:
plugins { // ... } def keystoreProperties = new Properties() // ... android { // ... }

문제 3: Firebase 패키지명 불일치

에러:
No matching client found for package name 'com.doythan.bookmarkmanager'
원인: Firebase에 새 패키지명 미등록
해결:
  1. Firebase Console에서 Android 앱 추가
  1. 패키지명: com.doythan.bookmarkmanager
  1. google-services.json 다운로드 및 교체

문제 4: MainActivity 패키지 구조 불일치

증상: Debug 모드 실행 시 "LinkOn keeps stopping"
원인:
  • Application ID 변경
  • 하지만 폴더 구조는 이전 패키지명
해결:
# 새 구조 생성 mkdir -p android/app/src/main/kotlin/com/doythan/bookmarkmanager # MainActivity.kt 생성 (새 패키지명으로) # 기존 폴더 삭제 rm -rf android/app/src/main/kotlin/com/example

문제 5: 에뮬레이터 저장 공간 부족

에러:
INSTALL_FAILED_INSUFFICIENT_STORAGE
해결:
Android Studio → Device Manager → 에뮬레이터 우측 ⋮ → Wipe Data

9️⃣ 빌드 최적화

Tree-shaking 효과

빌드 로그:
CupertinoIcons.ttf: 257KB → 848B (99.7% 감소) MaterialIcons-Regular.otf: 1.6MB → 3.5KB (99.8% 감소)
사용하지 않는 아이콘 자동 제거!

난독화 및 리소스 축소

build.gradle:
buildTypes { release { minifyEnabled true // 코드 난독화 shrinkResources true // 리소스 축소 } }
효과:
  • 앱 크기 감소
  • 역공학 방지
  • 보안 향상

🔟 보안 체크리스트

Git 커밋 전 확인

# key.properties가 추적되지 않는지 확인 git status # .gitignore 확인 cat .gitignore | grep key.properties

민감한 파일 목록

❌ 절대 커밋하면 안 됨: - android/key.properties - ~/upload-keystore.jks - *.jks - *.keystore ✅ 커밋해도 됨: - android/app/google-services.json (클라이언트 설정) - android/app/build.gradle - lib/firebase_options.dart

Keystore 백업

# 안전한 곳에 백업 (필수!) cp ~/upload-keystore.jks ~/Dropbox/backup/ # 또는 iCloud, Google Drive 등
중요:
  • Keystore를 잃어버리면 앱 업데이트 영구 불가능!
  • 비밀번호도 함께 기록!

1️⃣1️⃣ 최종 확인

빌드 파일 확인

# APK 확인 ls -lh build/app/outputs/flutter-apk/app-release.apk # -rw-r--r-- 50.1MB # AAB 확인 ls -lh build/app/outputs/bundle/release/app-release.aab # -rw-r--r-- 44.4MB # Keystore 확인 ls -la ~/upload-keystore.jks

서명 검증

jarsigner -verify -verbose -certs build/app/outputs/flutter-apk/app-release.apk
예상 결과 (마지막 줄):
jar verified.

패키지 정보 확인

~/Library/Android/sdk/build-tools/*/aapt dump badging build/app/outputs/flutter-apk/app-release.apk | grep package
결과:
package: name='com.doythan.bookmarkmanager' versionCode='1' versionName='1.0.0'

🎉 완료!

달성한 것들

✅ 앱 아이콘 설정 ✅ 앱 이름 설정 (LinkOn)Application ID 변경 ✅ 패키지 구조 정리 ✅ Keystore 생성 및 백업 ✅ 앱 서명 설정 ✅ Firebase 설정 업데이트 ✅ Debug 빌드 성공 ✅ Release APK 빌드 (50.1MB)Release AAB 빌드 (44.4MB) ✅ 에뮬레이터 테스트 완료 ✅ 트러블슈팅 완료

준비된 파일

📦 build/app/outputs/flutter-apk/app-release.apk (50.1MB) → 테스트용, 직접 설치 가능 📦 build/app/outputs/bundle/release/app-release.aab (44.4MB) → Play Store 업로드용 🔑 ~/upload-keystore.jks → 앱 서명 키 (백업 필수!) ⚙️ android/key.properties → 서명 설정 (Git 제외)

🚀 다음 단계 (#7 예고)

Google Play Console 등록 및 출시

필요한 것:
💰 Google Play Console 계정 ($25, 평생) 📸 스크린샷 4-8장 📝 앱 설명 작성 🎨 Feature Graphic (선택) 📋 개인정보처리방침 URL
진행 순서:
1. Play Console 계정 등록 2. 스크린샷 캡처 3. 앱 설명 작성 4. AAB 업로드 5. 내부 테스트 6. 프로덕션 출시 7. 심사 대기 (1-3일) 8. 출시 완료! 🎉

💡 배운 점

기술적 학습

Android 빌드:
  • Keystore 생성 및 관리
  • Gradle 설정 (Groovy vs Kotlin DSL)
  • 앱 서명 프로세스
  • Tree-shaking 최적화
패키지 관리:
  • Application ID 변경 프로세스
  • 패키지 구조와 폴더 구조 일치
  • Firebase 설정 동기화
보안:
  • 민감한 파일 관리
  • .gitignore 설정
  • Keystore 백업 중요성

트러블슈팅 경험

실전에서 겪은 문제들:
  1. Kotlin DSL vs Groovy 혼용
  1. plugins 블록 순서 문제
  1. Firebase 패키지명 불일치
  1. MainActivity 패키지 구조 불일치
  1. 에뮬레이터 저장 공간 부족
모든 문제를 해결하며 성장! 💪

📊 프로젝트 현황

완료된 플랫폼

Web: https://bookmark-manager-24e55.web.app 🚧 Android: 빌드 완료, Play Store 등록 대기 📋 iOS: 계획 단계

기술 스택

Framework: Flutter 3.35.4 Language: Dart 3.9.2 Backend: Firebase (Auth, Firestore, Hosting) State Management: Riverpod 3.0.3 Routing: go_router 16.2.4 Platform: Android, Web (iOS 예정)

📚 참고 자료

공식 문서

유용한 링크


🎓 학습 정리

이번 글에서 배운 것

빌드 프로세스:
  • ✅ Android 앱 빌드 설정
  • ✅ Keystore 생성 및 서명
  • ✅ Release 빌드 프로세스
  • ✅ APK vs AAB 차이
트러블슈팅:
  • ✅ Gradle 설정 문제 해결
  • ✅ Firebase 패키지명 동기화
  • ✅ 패키지 구조 정리
  • ✅ 실전 디버깅 경험
보안:
  • ✅ 민감한 정보 관리
  • ✅ Git 보안 설정
  • ✅ Keystore 백업

🙏 마치며

Android 앱 빌드 준비가 완료되었습니다!
많은 시행착오가 있었지만, 모든 문제를 하나씩 해결하며 실전 경험을 쌓을 수 있었습니다.
Share article

Gyeongwon's blog