Flavor와 환경 분리 — dev, staging, prod 빌드 관리
Flavor와 환경 분리 — dev, staging, prod 빌드 관리
실무에서는 개발, 스테이징, 프로덕션 환경을 분리해야 합니다. 각 환경마다 다른 API 서버, 앱 이름, 아이콘을 사용하는 것이 일반적입니다.
왜 환경 분리가 필요한가?
| 환경 | API 서버 | 앱 이름 | 용도 |
|---|---|---|---|
| dev | localhost:3000 | [DEV] MyApp | 개발 |
| staging | staging-api.example.com | [STG] MyApp | QA 테스트 |
| prod | api.example.com | MyApp | 실제 배포 |
방법 1: Dart define (간단한 방법)
# 빌드 시 환경 변수 전달
flutter run --dart-define=ENV=dev --dart-define=API_URL=http://localhost:3000
flutter run --dart-define=ENV=prod --dart-define=API_URL=https://api.example.com
# .env 파일로 관리
flutter run --dart-define-from-file=.env.dev
class AppConfig {
static const String env = String.fromEnvironment('ENV', defaultValue: 'dev');
static const String apiUrl = String.fromEnvironment(
'API_URL',
defaultValue: 'http://localhost:3000',
);
static bool get isDev => env == 'dev';
static bool get isProd => env == 'prod';
}
// 사용
final baseUrl = AppConfig.apiUrl;
if (AppConfig.isDev) {
// 개발 전용 로직
}
방법 2: Flutter Flavor (완전한 방법)
Flavor는 네이티브 빌드 시스템을 활용하여 앱 이름, 아이콘, 번들 ID까지 환경별로 분리합니다.
flutter_flavorizr로 자동 설정
# pubspec.yaml
dev_dependencies:
flutter_flavorizr: ^2.2.0
flavorizr:
flavors:
dev:
app:
name: "[DEV] MyApp"
android:
applicationId: "com.example.app.dev"
ios:
bundleId: "com.example.app.dev"
staging:
app:
name: "[STG] MyApp"
android:
applicationId: "com.example.app.staging"
ios:
bundleId: "com.example.app.staging"
prod:
app:
name: "MyApp"
android:
applicationId: "com.example.app"
ios:
bundleId: "com.example.app"
# 자동 설정 실행
dart run flutter_flavorizr
빌드 및 실행
# 개발 환경으로 실행
flutter run --flavor dev -t lib/main_dev.dart
# 스테이징 환경으로 빌드
flutter build apk --flavor staging -t lib/main_staging.dart
# 프로덕션 빌드
flutter build appbundle --flavor prod -t lib/main_prod.dart
환경별 진입점
// lib/config/app_config.dart
enum Environment { dev, staging, prod }
class AppConfig {
final Environment environment;
final String apiBaseUrl;
final String appTitle;
final bool enableLogging;
const AppConfig({
required this.environment,
required this.apiBaseUrl,
required this.appTitle,
this.enableLogging = false,
});
bool get isDev => environment == Environment.dev;
bool get isProd => environment == Environment.prod;
}
// lib/main_dev.dart
void main() {
const config = AppConfig(
environment: Environment.dev,
apiBaseUrl: 'http://localhost:3000',
appTitle: '[DEV] MyApp',
enableLogging: true,
);
runApp(MyApp(config: config));
}
// lib/main_staging.dart
void main() {
const config = AppConfig(
environment: Environment.staging,
apiBaseUrl: 'https://staging-api.example.com',
appTitle: '[STG] MyApp',
enableLogging: true,
);
runApp(MyApp(config: config));
}
// lib/main_prod.dart
void main() {
const config = AppConfig(
environment: Environment.prod,
apiBaseUrl: 'https://api.example.com',
appTitle: 'MyApp',
enableLogging: false,
);
runApp(MyApp(config: config));
}
앱에서 config 접근
class MyApp extends StatelessWidget {
final AppConfig config;
const MyApp({super.key, required this.config});
@override
Widget build(BuildContext context) {
return Provider.value(
value: config,
child: MaterialApp(
title: config.appTitle,
home: const HomeScreen(),
),
);
}
}
// 하위 위젯에서 접근
final config = context.read<AppConfig>();
print(config.apiBaseUrl);
환경별 Firebase 설정
// 환경별 다른 Firebase 프로젝트 연결
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Flavor에 따라 다른 Firebase 옵션
await Firebase.initializeApp(
options: _getFirebaseOptions(),
);
runApp(const MyApp());
}
FirebaseOptions _getFirebaseOptions() {
const flavor = String.fromEnvironment('FLAVOR');
switch (flavor) {
case 'dev':
return DevFirebaseOptions.currentPlatform;
case 'staging':
return StagingFirebaseOptions.currentPlatform;
default:
return ProdFirebaseOptions.currentPlatform;
}
}
환경별 앱 아이콘
# pubspec.yaml
dev_dependencies:
flutter_launcher_icons: ^0.14.0
# flutter_launcher_icons-dev.yaml
flutter_launcher_icons:
android: true
ios: true
image_path: "assets/icons/icon_dev.png"
# flutter_launcher_icons-prod.yaml
flutter_launcher_icons:
android: true
ios: true
image_path: "assets/icons/icon_prod.png"
dart run flutter_launcher_icons -f flutter_launcher_icons-dev.yaml
dart run flutter_launcher_icons -f flutter_launcher_icons-prod.yaml
VS Code 런 설정
// .vscode/launch.json
{
"configurations": [
{
"name": "Dev",
"request": "launch",
"type": "dart",
"program": "lib/main_dev.dart",
"args": ["--flavor", "dev"]
},
{
"name": "Staging",
"request": "launch",
"type": "dart",
"program": "lib/main_staging.dart",
"args": ["--flavor", "staging"]
},
{
"name": "Production",
"request": "launch",
"type": "dart",
"program": "lib/main_prod.dart",
"args": ["--flavor", "prod"]
}
]
}
정리
- dart-define: 간단한 환경 변수 전달에 적합합니다
- Flavor: 앱 이름, 번들 ID, 아이콘까지 완전히 분리할 때 사용합니다
flutter_flavorizr로 네이티브 설정을 자동 생성할 수 있습니다- 환경별 진입점(
main_dev.dart,main_prod.dart)으로 config를 분리합니다 - 같은 기기에 dev와 prod 앱을 동시에 설치하려면 번들 ID를 다르게 해야 합니다
- Firebase도 환경별 다른 프로젝트를 연결하는 것이 좋습니다
댓글 로딩 중...