개요

Google Play store에 app을 올리기 위해선 key로 서명을 해야 한다.
평소에는 android studio을 이용해 generate signed appbundle 기능으로 android studio에서 signed app bundle을 만들었다.
그런데 flutter에서 .env파일 사용 시 dotenv을 사용하면 apk에서 .env 값을 볼 수 있다는 글을 보았다.
따라서 flutter_dotenv패키지를 전면 교체 후 build시에 커멘드라인에서 주입하는 방식으로 교체했다.
--dart-define-fron-file=.env
이걸 build 명령시 인자에 추가해주면 더이상 .env을 asset으로 포함시키지 않아도 된다.
명령어는 다음과 같다.
# run flutter
flutter run --dart-define-from-file=.env
# run without debug
flutter run --release --dart-define-from-file=.env
# build appbundle
flutter build appbundle --release --dart-define-from-file=.env
vscode launch.json 설정
또한 vscode 단축키를 눌러 (ctrl + shift + f5) 빌드를 자주 하는데, 이때도 추가로 인자를 넣어줘야 한다.
다음과 같이 설정해서 .env 인자를 추가할 수 있다.
{
"version": "0.2.0",
"configurations": [
{
"name": "Flutter",
"request": "launch",
"type": "dart",
"args": [
"--dart-define-from-file=.env"
]
},
{
"name": "Flutter (Run Without Debugging)", // 새 구성 추가
"request": "launch",
"type": "dart",
"noDebug": true, // 디버그 모드 비활성화
"args": [
"--dart-define-from-file=.env"
]
}
]
}
참고 자료
Flutter 앱 배포 가이드: 환경변수와 함께 Android & iOS 스토어에 배포하기
문제 상황

1. flutter build appbundle --release --dart-define-from-file=.env으로 빌드시 google play에 appbundle을 업로드하면 서명이 되지 않았다고 뜬다.
사진에서도 '업로드된 모든 번들에 서명해야 합니다' 라고 에러가 떠있는걸 볼 수 있다.
app/build.gradle.kts 파일도 잘 설정을 해줬었는데 이때는 뭐가 문제였는지 몰랐다.
2. 또한 기존에는 android studio로 appbundle을 빌드했는데, 이제는 --dart--define-from-file 커맨드가 적용되지 않아 .env파일 주입을 할 수 없었다. (빌드는 되지만, appbundle 업로드 후 test을 받으면 .env파일이 빠져있다.)
3. 또한 flutter 공식 홈페이지에서도 android studio을 이용하는 방법 말고는 제대로 appbundle 서명 및 빌드 방법을 알려주지 않는다.
Build and release an Android app
Build and release an Android app
How to prepare for and release an Android app to the Play store.
docs.flutter.dev
앱 서명 | Android Studio | Android Developers
앱 서명 | Android Studio | Android Developers
앱 서명 및 보안과 관련된 중요한 개념을 알아보고, Android 스튜디오를 사용하여 Google Play에 출시하기 위해 앱에 서명하는 방법과 Play 앱 서명을 선택하는 방법을 알아보세요.
developer.android.com
해결 전 build.gradle.kts (일부)
...
val keystoreProperties = Properties()
val keystorePropertiesFile = rootProject.file("key.properties")
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
}
...
android {
...
signingConfigs {
create("release") {
if (keystorePropertiesFile.exists() && keystoreProperties["storeFile"] != null) {
keyAlias = keystoreProperties["keyAlias"] as String
keyPassword = keystoreProperties["keyPassword"] as String
storeFile = file(keystoreProperties["storeFile"] as String)
storePassword = keystoreProperties["storePassword"] as String
}
}
}
buildTypes {
release{
if (keystorePropertiesFile.exists() && keystoreProperties["storeFile"] != null) {
signingConfig = signingConfigs.getByName("release")
}
isMinifyEnabled = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
...
원인


build.gradle.kts에서 key.properties 파일 경로를 찾을 때, file() 메서드를 사용하는데 이게 android/ 폴더가 기본이라고 한다.
따라서 key.properties 파일을 이제까지 찾지 못하고 있던 거였고, 따라서 서명이 되지 않고 있는 거였다.

서명이 제대로 되는 로그는 이렇게 나온다. 로그는 println문을 따로 추가했다.

또한 key 파일이 들어가지 않고 있던걸 다음 로그를 통해 알 수 있었다. key.properties 파일을 불러오는 if문과 else에 각각 성공/실패시 출력을 추가했더니 해당 파일이 들어가지 않고 있단걸 확실히 알 수 있었다.
해결 방법
val keystoreProperties = Properties()
val keystorePropertiesFile = rootProject.file("app/key.properties")
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
println("key.properties file found and loaded successfully.")
}else{
println("key.properties file not found. Using default signing configuration.")
}
다음과 같이 build.gradle.kts에서 key.properties 파일 경로를 수정해줬다. key파일 출력문도 추가해준 것을 볼 수 있다.
key.properties 파일을 불러오는 if문과 else문을 추가해준것도 해당 부분이다.

appbundle이 빌드될 때 key 파일이 잘 들어가고, 문제없이 빌드되는걸 볼 수 있다.

참고로 key파일 password가 틀렸을 땐 다음과 같이 비밀번호가 틀렸다고 로그가 나온다.
해결 후 build.gradke.kts 파일
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import java.util.Properties
import java.io.FileInputStream
plugins {
id("com.android.application")
// START: FlutterFire Configuration
id("com.google.gms.google-services")
// END: FlutterFire Configuration
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}
val keystoreProperties = Properties()
val keystorePropertiesFile = rootProject.file("app/key.properties")
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
println("key.properties file found and loaded successfully.")
}else{
println("key.properties file not found. Using default signing configuration.")
}
android {
namespace = "app_name"
compileSdk = 36
ndkVersion = "27.3.13750724"
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
defaultConfig {
// AndroidManifest.xml에서 ${applicationName} 플레이스홀더를 대체
manifestPlaceholders += mapOf("applicationName" to "io.flutter.app.FlutterApplication")
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.yeoseyo.byeolme"
minSdk = 24
targetSdk = 36
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
versionCode = flutter.versionCode
versionName = flutter.versionName
}
signingConfigs {
create("release") {
if (keystorePropertiesFile.exists() && keystoreProperties["storeFile"] != null) {
keyAlias = keystoreProperties["keyAlias"] as String
keyPassword = keystoreProperties["keyPassword"] as String
storeFile = file(keystoreProperties["storeFile"] as String)
storePassword = keystoreProperties["storePassword"] as String
}else{
println("WARNING: key.properties file not found or storeFile not set")
logger.warn("key.properties file not found or storeFile not set")
}
}
}
buildTypes {
release{
if (keystorePropertiesFile.exists() && keystoreProperties["storeFile"] != null) {
signingConfig = signingConfigs.getByName("release")
}
isMinifyEnabled = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
// buildTypes {
// release {
// // TODO: Add your own signing config for the release build.
// // Signing with the debug keys for now, so `flutter run --release` works.
// signingConfig = signingConfigs.getByName("debug")
// }
// }
}
kotlin {
compilerOptions {
jvmTarget = JvmTarget.fromTarget("17")
}
}
flutter {
source = "../.."
}
//dependencies {
// // Firebase BoM을 통한 버전 관리 - 모든 Firebase 라이브러리의 호환성을 보장
// implementation(platform("com.google.firebase:firebase-bom:34.1.0"))
//
// // Firebase Analytics - 앱 사용 통계 및 분석
// implementation("com.google.firebase:firebase-analytics")
//
// // Firebase Firestore - 실시간 NoSQL 데이터베이스
// implementation("com.google.firebase:firebase-firestore")
//
// // 추가 Firebase 서비스는 필요시 아래에 추가
// // Firebase Auth: implementation("com.google.firebase:firebase-auth")
// // Firebase Storage: implementation("com.google.firebase:firebase-storage")
// // Firebase Messaging: implementation("com.google.firebase:firebase-messaging")
//}
결과

이렇게 해서 문제를 해결할 수 있었다.
느낀 점
- 해당 문제를 찾으면서 영어로 자료를 찾아도 잘 나오지 않았었다. ai의 발전으로 stackoverflow 및 기타 커뮤니티에 글이 올라오지 않게 된 부작용이다.
- 다만 ai을 이용해서 문제의 원인을 추론하고, 해결할 수 있었다. gemini가 file()이 android/가 기본 폴더라는걸 알려주지 않았더라면 많이 어려웠을 것이다.
- 나중에 발견했는데 flutter 공식 홈페이지에선 key.properties 파일을 android 경로에 올리라고 나와 있었다... 항상 공식 문서를 잘 읽어보자.

4. build appbundle
'문제해결' 카테고리의 다른 글
| [Nginx Troubleshooting] HTTPS 설정 후 403 Forbidden 에러 해결기 (0) | 2025.09.30 |
|---|---|
| flutter 현재 java-gradle 버전 호환되는지 확인 명령어 (2) | 2025.07.27 |
| [react] useRef 사용 못할 때, createRef (0) | 2025.07.04 |
| [vite/react/typescript] 프로젝트에 절대경로 적용하기 (1) | 2025.06.26 |
| [ollama web-ui] python 지원 버전 다를때 해결법 (3.13 vs 3.11) (0) | 2025.01.22 |