Flavors & Environment #
Aplikasi produksi selalu membutuhkan lebih dari satu environment: development untuk eksperimen, staging untuk QA, production untuk pengguna nyata. Setiap environment punya URL API yang berbeda, API key berbeda, dan mungkin nama app serta ikon yang berbeda agar mudah dibedakan. Flutter mendukung ini via flavors (konfigurasi per environment di level native) dan --dart-define (variabel yang di-inject saat build).
Pendekatan: dart-define (Paling Sederhana) #
Untuk proyek kecil hingga menengah, --dart-define sudah cukup tanpa perlu setup flavor native yang kompleks:
# Jalankan dengan variabel environment
flutter run \
--dart-define=APP_ENV=development \
--dart-define=API_URL=https://api-dev.contoh.com \
--dart-define=API_KEY=dev-key-abc123 \
--dart-define=ENABLE_LOGS=true
# Build release staging
flutter build apk --release \
--dart-define=APP_ENV=staging \
--dart-define=API_URL=https://api-staging.contoh.com \
--dart-define=API_KEY=staging-key-xyz789 \
--dart-define=ENABLE_LOGS=true
# Build release production
flutter build appbundle --release \
--dart-define=APP_ENV=production \
--dart-define=API_URL=https://api.contoh.com \
--dart-define=API_KEY=prod-key-secret \
--dart-define=ENABLE_LOGS=false
AppConfig — Akses Variabel di Dart #
// lib/core/config/app_config.dart
enum AppEnvironment { development, staging, production }
class AppConfig {
// Baca dari --dart-define, fallback ke nilai default
static const String _env = String.fromEnvironment(
'APP_ENV',
defaultValue: 'development',
);
static const String apiUrl = String.fromEnvironment(
'API_URL',
defaultValue: 'https://api-dev.contoh.com',
);
static const String apiKey = String.fromEnvironment(
'API_KEY',
defaultValue: '',
);
static const bool enableLogs = bool.fromEnvironment(
'ENABLE_LOGS',
defaultValue: true,
);
// Environment saat ini
static AppEnvironment get environment => switch (_env) {
'staging' => AppEnvironment.staging,
'production' => AppEnvironment.production,
_ => AppEnvironment.development,
};
static bool get isDevelopment => environment == AppEnvironment.development;
static bool get isStaging => environment == AppEnvironment.staging;
static bool get isProduction => environment == AppEnvironment.production;
// Nilai yang berbeda per environment
static int get cacheTimeout => switch (environment) {
AppEnvironment.development => 0, // no cache saat dev
AppEnvironment.staging => 5, // 5 menit
AppEnvironment.production => 30, // 30 menit
};
static String get appName => switch (environment) {
AppEnvironment.development => '[DEV] Toko Saya',
AppEnvironment.staging => '[STG] Toko Saya',
AppEnvironment.production => 'Toko Saya',
};
}
File Konfigurasi per Environment #
# Simpan konfigurasi di file dart-define
# Jangan commit ke git! Tambahkan ke .gitignore
# .dart_define/development.json
{
"APP_ENV": "development",
"API_URL": "https://api-dev.contoh.com",
"API_KEY": "dev-key-abc123",
"ENABLE_LOGS": "true"
}
# .dart_define/production.json
{
"APP_ENV": "production",
"API_URL": "https://api.contoh.com",
"API_KEY": "prod-key-secret",
"ENABLE_LOGS": "false"
}
# Jalankan dengan file JSON
flutter run --dart-define-from-file=.dart_define/development.json
flutter build appbundle --dart-define-from-file=.dart_define/production.json
Flutter Flavorizr — Setup Flavor Native #
Untuk proyek yang butuh nama app, ikon, dan package name yang berbeda per environment, gunakan flutter_flavorizr:
# pubspec.yaml -- hanya di dev_dependencies
dev_dependencies:
flutter_flavorizr: ^2.2.3
# pubspec.yaml -- konfigurasi flavor
flavorizr:
app:
android:
flavorDimensions: "version"
ios: {}
flavors:
development:
app:
name: "[DEV] Toko Saya"
android:
applicationId: "com.contoh.tokosaya.dev"
icon: "assets/icons/icon_dev.png"
ios:
bundleId: "com.contoh.tokosaya.dev"
icon: "assets/icons/icon_dev.png"
staging:
app:
name: "[STG] Toko Saya"
android:
applicationId: "com.contoh.tokosaya.staging"
icon: "assets/icons/icon_staging.png"
ios:
bundleId: "com.contoh.tokosaya.staging"
icon: "assets/icons/icon_staging.png"
production:
app:
name: "Toko Saya"
android:
applicationId: "com.contoh.tokosaya"
icon: "assets/icons/icon_production.png"
ios:
bundleId: "com.contoh.tokosaya"
icon: "assets/icons/icon_production.png"
# Generate semua konfigurasi native
flutter pub run flutter_flavorizr
Entry Point Terpisah per Flavor #
Untuk perbedaan yang lebih besar antar environment (mock service, debug panel):
lib/
main.dart ← fallback / production
main_development.dart ← development entry point
main_staging.dart ← staging entry point
main_production.dart ← production entry point
// lib/main_development.dart
import 'package:flutter/material.dart';
import 'app.dart';
import 'core/config/app_config.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Development-specific setup
if (AppConfig.isDevelopment) {
// Gunakan mock HTTP client
// Aktifkan debug panel
// Nonaktifkan crash reporting
}
runApp(const App());
}
// lib/main_production.dart
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'app.dart';
import 'firebase_options.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
runApp(const App());
}
# Jalankan dengan entry point tertentu
flutter run -t lib/main_development.dart
flutter build apk --release -t lib/main_production.dart
Konfigurasi VS Code dan Android Studio #
// .vscode/launch.json
{
"configurations": [
{
"name": "Development",
"request": "launch",
"type": "dart",
"program": "lib/main_development.dart",
"args": ["--dart-define-from-file=.dart_define/development.json"]
},
{
"name": "Staging",
"request": "launch",
"type": "dart",
"program": "lib/main_staging.dart",
"args": ["--dart-define-from-file=.dart_define/staging.json"]
},
{
"name": "Production",
"request": "launch",
"type": "dart",
"flutterMode": "release",
"program": "lib/main_production.dart",
"args": ["--dart-define-from-file=.dart_define/production.json"]
}
]
}
Keamanan Konfigurasi #
JANGAN:
✗ Commit file .dart_define/*.json ke git (berisi API key!)
✗ Hardcode API key di kode sumber
✗ Simpan secret di pubspec.yaml
LAKUKAN:
✓ Tambahkan .dart_define/ ke .gitignore
✓ Simpan secret di environment variable CI/CD (GitHub Secrets)
✓ Buat file .dart_define/template.json tanpa nilai sensitif sebagai panduan
# .gitignore
.dart_define/
*.jks
*.p12
*.keystore
google-services.json
GoogleService-Info.plist
Ringkasan #
--dart-defineadalah cara termudah untuk inject konfigurasi per environment tanpa setup native yang kompleks. Gunakan--dart-define-from-filedengan file JSON untuk konfigurasi yang lebih banyak.AppConfigmemusatkan semua konfigurasi dalam satu class — akses viaAppConfig.apiUrl,AppConfig.isProduction, dll. Ini mencegah magic string yang tersebar.- Untuk perbedaan lebih dalam (nama app, ikon, package ID berbeda), gunakan
flutter_flavorizryang menggenerate konfigurasi Android/iOS secara otomatis.- Entry point terpisah (
main_development.dart,main_production.dart) memungkinkan konfigurasi yang benar-benar berbeda per environment — mock service, debug panel, crash reporter.- Jangan commit file konfigurasi yang berisi API key dan secret ke git. Simpan di environment variable CI/CD dan generate saat build.
- Konfigurasi VS Code launch.json dan Android Studio run configuration agar tim bisa dengan mudah berpindah antar environment dengan satu klik.