flavorizr
빌드 환경을 분리를 위한 flutter_flavorizr의 거의 모든 것읽는데 11분 정도 걸려요.이 포스트는 계속해서 업데이트 됩니다.
유용한 댓글이나 경험을 남겨주시면 해당 내용도 추가하겠습니다!
Getting Started
flutter_flavorizr의 사용방법에 대해 설명합니다.
pubspec.yaml
해당 파일에서 하단에 flavorizr
필드를 작성합니다.
# Flavor settings
flavorizr:
flavors:
dev:
app:
name: "DEV APP_NAME"
android:
applicationId: "dev.package.name.app_name"
# icon: "assets/app_icon.png"
firebase:
config: "android/firebase/dev/google-services.json"
ios:
bundleId: "dev.bundle.id.appName"
# icon: "assets/app_icon.png"
firebase:
config: "ios/firebase/dev/GoogleService-Info.plist"
prod:
app:
name: "APP_NAME"
android:
applicationId: "package.name.app_name"
# icon: "assets/app_icon.png"
firebase:
config: "android/firebase/prod/google-services.json"
ios:
bundleId: "dev.bundle.id.appName"
# icon: "assets/app_icon.png"
firebase:
config: "ios/firebase/prod/GoogleService-Info.plist"
위와 같은 경우에는 dev
, prod
두 개의 flavor 환경을 구축하는 경우입니다.
다음은 아래에 추가로 필드를 기입하면 됩니다.
공통사항은 app, AOS 전용사항은 android, iOS 전용사항은 ios 아래에 기입하면 됩니다.
자세한 내용은 flavorizr의 공식 문서를 참고하시면 좋습니다.
CLI
pubspec.yaml 작성을 완료했다면, 아래의 명령어를 사용하면 자동 빌드됩니다.
dart run flutter_flavorizr
빌드가 완료되면 아래의 파일들이 자동 생성 및 자동 수정될 것 입니다.
-
main.dart
-
main_prod.dart
prod 빌드 환경에 맞는 초기 세팅을 진행하는 파일입니다.main_prod.dart// Import from prod dependency import 'package:flutter/material.dart'; import 'flavors.dart'; import 'main.dart' as runner; Future<void> main() async { F.appFlavor = Flavor.prod; await runner.main(); }
-
main_dev.dart
dev 빌드 환경에 맞는 초기 세팅을 진행하는 파일입니다.main_dev.dart// Import from dev dependency import 'package:flutter/material.dart'; import 'flavors.dart'; import 'main.dart' as runner; Future<void> main() async { F.appFlavor = Flavor.dev; await runner.main(); }
-
flavors.dart
flavor 관련 static field를 관리하는 클래스 파일입니다.flavors.dartenum Flavor { dev, prod, } class F { static Flavor? appFlavor; static String get name => appFlavor?.name ?? ''; static String get title { switch (appFlavor) { case Flavor.dev: return 'DEV APP_NAME'; case Flavor.prod: return 'APP_NAME'; default: return 'title'; } } }
-
pages/my_home_page.dart
VSCode debug setting
디버깅 시 아래의 명령어를 사용해서 실행할 수 있습니다.
flutter run --flavor prod -t lib/main_prod.dart
flutter run --flavor dev -t lib/main_dev.dart
하지만, VSCode의 디버깅 탭을 100% 활용하기 위해 추가 설정을 진행하면 좋습니다.
우선, 프로젝트 폴더에서 .vscode
폴더를 생성, 내부에 아래의 파일을 추가합니다.
{
"configurations": [
{
"name": "dev",
"request": "launch",
"type": "dart",
"args": ["--flavor", "dev"],
"program": "lib/main_dev.dart"
},
{
"name": "prod",
"request": "launch",
"type": "dart",
"args": ["--flavor", "prod"],
"program": "lib/main_prod.dart"
}
]
}
이렇게 설정하면 명령어 없이 디버깅 탭에서 버튼을 통해 실행할 수 있습니다.
Tips & Addon(?)
flavor를 사용할 때 추가로 구현하면 좋은 것들, 혹은 firebase와 같은 추가 설정이 필요한 부분에 대해 설명합니다.
Config
flavor의 주된 사용 목적에는 prod, dev 에서의 API endpoint를 다르게 설정하는 등, 앱 내부에서 상수들을 별도로 관리하는데에 있을 것 입니다.
따라서 이러한 처리를 자동으로 하도록 Config 객체를 만들어 관리하는 방법에 대해 소개하겠습니다.
우선 코드 전문부터 보겠습니다.
import 'package:child_goods_store_flutter/constants/networks.dart';
import 'package:child_goods_store_flutter/flavors.dart';
class Configs {
// Add configs here
final String baseUrl;
// Initialize configs here
Config._dev() : baseUrl = Networks.devBaseUrl;
Config._prod() : baseUrl = Networks.baseUrl;
factory Config(Flavor? flavor) {
switch (flavor) {
case Flavor.dev:
_instance = Config._dev();
break;
case Flavor.prod:
_instance = Config._prod();
break;
default:
_instance = Config._dev();
break;
}
return instance;
}
static Config? _instance;
static Config get instance => _instance ?? Config(F.appFlavor ?? Flavor.dev);
}
위 코드는 일단 Configs 싱글톤 객체를 생성하는 파일입니다.
그 과정에서 기본적으로 F(lavor) 클래스의 appFlavor static 필드를 생성자에 주입 받습니다.
즉, Configs.instance
를 호출하는 것 만으로토 현재 실행 환경의 flavor가 적용된 Configs 싱글톤 객체를 반환받을 수 있습니다.
여기에 추가로 본인이 원하는 필드(e.g. baseUrl)를 추가하고, 각 생성자에서 각 환경에 맞게 초기화 해주시면 됩니다.
그럼 앞으로 Configs.instance.baseUrl
를 사용하는 것 만으로도 빌드 환경에 따라 다른 값이 알아서 적용되게 됩니다.
Firebase
firebase의 google-services.json
이나, GoogleService-Info.plist
를 빌드 환경에 맞게 알아서 처리하는 방법도 알아봅시다.
firebase 프로젝트 및 service 파일을 빌드 환경에 맞게 분리하면 좋은게,
가끔 Test 라며 푸쉬 알림이 실 사용자에게 전송되는 일을 예방할 수 있기 때문입니다.
우선 firebase 프로젝트를 생성합니다.
자세한 생성 방법 및 flutterfire CLI 설치 방법은 생략합니다.
다음은 프로젝트 폴더에서 아래의 CLI 명령어를 실행하여 firebase 프로젝트에 flutter 앱을 추가해줍니다.
flutterfire config \
--project=firebase_project_name \
--ios-bundle-id=bundle.id.appName \
--android-package-name=package.name.app_name
위 명령어는 firebase_project_name firebase 프로젝트에 prod 빌드 환경의 앱을 추가하는 명령어 입니다.
-
만약 같은 firebase 프로젝트에 dev 빌드 환경의 앱을 추가하려면 아래의 명령어를 입력하면 됩니다.
bashflutterfire config \ --project=firebase_project_name \ --ios-bundle-id=dev.bundle.id.appName \ --android-package-name=dev.package.name.app_name
2번째 부터
ios/firebase_app_id_file.json
파일이 중복된다는 경고가 나오는데,
이는 개발환경에 무관하게 동일한 파일이기 때문에 override 해도 상관 없습니다. -
만약 다른 firebase 프로젝트에 dev 빌드 환경의 앱을 추가하려면 아래의 명령어를 입력하면 됩니다.
bashflutterfire config \ --project=dev_firebase_project_name \ --ios-bundle-id=dev.bundle.id.appName \ --android-package-name=dev.package.name.app_name
성공적으로 추가 되었다면, 자동으로 생성되는 google-services.json
이나, GoogleService-Info.plist
는 일단 제거해줍니다.
여기까지 성공했다면 firebase 프로젝트에 4개의 앱이 추가되었을 것입니다.
(AOS prod, dev / iOS prod, dev)
이제 google-services.json
, GoogleService-Info.plist
파일을 다운로드 받아 pubspec.yaml
파일에 적힌 경로에 각각 다운로드 합니다.
# Flavor settings
flavorizr:
flavors:
dev:
app:
name: "DEV APP_NAME"
android:
applicationId: "dev.package.name.app_name"
# icon: "assets/app_icon.png"
firebase:
config: "android/firebase/dev/google-services.json"
ios:
bundleId: "dev.bundle.id.appName"
# icon: "assets/app_icon.png"
firebase:
config: "ios/firebase/dev/GoogleService-Info.plist"
prod:
app:
name: "APP_NAME"
android:
applicationId: "package.name.app_name"
# icon: "assets/app_icon.png"
firebase:
config: "android/firebase/prod/google-services.json"
ios:
bundleId: "dev.bundle.id.appName"
# icon: "assets/app_icon.png"
firebase:
config: "ios/firebase/prod/GoogleService-Info.plist"
해당 경로는 본인 프로젝트의 파일 관리 정책에 맞게 알아서 설정해주시면 됩니다.
그리고 다시 아래의 명령어를 실행하면 알아서 firebase 환경 설정이 완료됩니다.
dart run flutter_flavorizr
반드시 관련 파일은 .gitignore에 추가하여 보안에 유의합시다.
# Secret - Common
lib/configs/firebase_options.dart
lib/configs/firebase_options_dev.dart
# Secret - Android
android/firebase
android/app/src/prod/google-services.json
android/app/src/dev/google-services.json
# Secret - iOS
ios/firebase
ios/firebase_app_id_file.json
ios/Runner/prod/GoogleService-Info.plist
ios/Runner/dev/GoogleService-Info.plist
# Secret - iOS auto generated
ios/Runner/GoogleService-Info.plist
마지막으로 main_prod.dart
, main_dev.dart
에 초기화 설정을 해주면 끝입니다.
// Import from prod dependency
import 'package:child_goods_store_flutter/configs/firebase_options.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'flavors.dart';
import 'main.dart' as runner;
Future<void> main() async {
F.appFlavor = Flavor.prod;
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
await runner.main();
}
// Import from dev dependency
import 'package:child_goods_store_flutter/configs/firebase_options_dev.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'flavors.dart';
import 'main.dart' as runner;
Future<void> main() async {
F.appFlavor = Flavor.dev;
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
await runner.main();
}
(자세히 보면 firebase_options.dart 파일의 import 경로가 다릅니다)
TroubleShoots
글쓴이가 실제로 해보며 겪은 문제와 그에 대한 해결법에 대해 정리해봤습니다.
Unable to load contents of file list
글쓴이는 아래와 같은 에러를 본 적이 있습니다.
Error (Xcode): Unable to load contents of file list: '/Target Support Files/Pods-Runner/Pods-Runner-frameworks-Debug-dev-input-files.xcfilelist'
Error (Xcode): Unable to load contents of file list: '/Target Support Files/Pods-Runner/Pods-Runner-frameworks-Debug-dev-output-files.xcfilelist'
해당 에러는 특정 파일이 존재하지 않을 때 발생하는 에러로, flavorizr 생성 CLI 명렁어를 실행했음에도 생성되지 않는 경우가 종종 있었습니다.
그럴 경우에는 아래의 스탭을 천천히 밟아가면 대부분 해결될 것입니다.
-
ios → Xcode로 열기 → 상단 상태바에서 Product → Clean Build Folder
-
아래 명령어로 pod 업데이트, depencency 초기화 및 재설치
bashcd ios/ sudo gem update cocoapods --pre pod update pod repo update pod deintegrate pod install
-
flavorizr 명령어 재실행
bashdart run flutter_flavorizr
-
ios → Xcode로 열기 → Runner(PROJECT) → Info → Configurations 의 파일이 잘 설정되어있는지 확인
-
아래의 파일의 include가 아래와 같이 작성되어있는지 확인
ios/Flutter/Debug.xcconfig#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig"
ios/Flutter/Release.xcconfig#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig"
FirebaseCommandException
글쓴이는 아래와 같은 에러를 본 적이 있습니다.
FirebaseCommandException: An error occured on the Firebase CLI when attempting to run a command.
COMMAND: firebase apps:create ios child_goods_store_flutter (ios) --bundle-id=dev.io.github.u3C1S.child_goods_store_flutter --json --project=child-goods-store
ERROR: Failed to create iOS app for project child-goods-store. See firebase-debug.log for more info.
이 경우에는 iOS bundleId가 snake_case로 작성되어서 발생한 문제였습니다.
반드시, iOS bundleId는 camelCase로 작성하도록 합시다.