Perché servono ambienti multipli
Quando un'app Flutter cresce, è indispensabile poter distinguere tra diversi ambienti di esecuzione: sviluppo (dev), staging (collaudo) e produzione (prod). Ogni ambiente ha tipicamente:
- Un endpoint API differente (
api.dev.miaapp.comvsapi.miaapp.com) - Un
applicationId/ bundle identifier diverso, così da installare più versioni sullo stesso dispositivo - Nomi e icone distinti per riconoscere a colpo d'occhio quale build stai usando
- Chiavi di analytics o crash reporting separate
In Flutter questa separazione si ottiene combinando i flavor nativi (Android) e le build configuration/scheme (iOS) con una configurazione a livello Dart tramite gli entry point e i dart-define.
Configurare i flavor su Android
Apri android/app/build.gradle e aggiungi i productFlavors all'interno del blocco android:
android {
// ...
flavorDimensions "env"
productFlavors {
dev {
dimension "env"
applicationIdSuffix ".dev"
resValue "string", "app_name", "MiaApp Dev"
}
staging {
dimension "env"
applicationIdSuffix ".staging"
resValue "string", "app_name", "MiaApp Staging"
}
prod {
dimension "env"
resValue "string", "app_name", "MiaApp"
}
}
}
Nel AndroidManifest.xml usa la risorsa dinamica per il nome dell'app:
<application
android:label="@string/app_name"
... >
Grazie all'applicationIdSuffix potrai avere com.miaazienda.miaapp.dev, .staging e la versione di produzione installate contemporaneamente.
Configurare le scheme su iOS
Su iOS il processo è più manuale. In Xcode:
- Duplica la configurazione
Debug,ReleaseeProfileper ogni ambiente (es.Debug-dev,Release-prod, ecc.). - Crea uno Scheme per ogni flavor tramite Product > Scheme > Manage Schemes.
- Definisci una User-Defined Setting (es.
APP_NAMEeBUNDLE_SUFFIX) e usala nelInfo.plistcon$(APP_NAME).
Imposta il PRODUCT_BUNDLE_IDENTIFIER di ogni configurazione, ad esempio com.miaazienda.miaapp.dev per l'ambiente di sviluppo.
Gli entry point Dart
Crea un file di configurazione condiviso e più main distinti. Prima definiamo la configurazione:
// lib/config/app_config.dart
enum Environment { dev, staging, prod }
class AppConfig {
final Environment environment;
final String apiBaseUrl;
final String appName;
const AppConfig({
required this.environment,
required this.apiBaseUrl,
required this.appName,
});
static late AppConfig instance;
}
Ora gli entry point, uno per ambiente:
// lib/main_dev.dart
import 'package:flutter/material.dart';
import 'config/app_config.dart';
import 'app.dart';
void main() {
AppConfig.instance = const AppConfig(
environment: Environment.dev,
apiBaseUrl: 'https://api.dev.miaapp.com',
appName: 'MiaApp Dev',
);
runApp(const MyApp());
}
// lib/main_prod.dart
import 'package:flutter/material.dart';
import 'config/app_config.dart';
import 'app.dart';
void main() {
AppConfig.instance = const AppConfig(
environment: Environment.prod,
apiBaseUrl: 'https://api.miaapp.com',
appName: 'MiaApp',
);
runApp(const MyApp());
}
Nel resto del codice accedi alla configurazione tramite AppConfig.instance.apiBaseUrl.
Eseguire e compilare i flavor
Per avviare o compilare un flavor specifico usa i flag --flavor e --target:
# Avvio in sviluppo
flutter run --flavor dev --target lib/main_dev.dart
# Build di produzione (Android)
flutter build appbundle --flavor prod --target lib/main_prod.dart
# Build iOS di staging
flutter build ipa --flavor staging --target lib/main_staging.dart
Alternativa: --dart-define
Se preferisci evitare più file main, puoi passare variabili a compile-time con --dart-define:
flutter run --dart-define=ENV=dev --dart-define=API_URL=https://api.dev.miaapp.com
E leggerle in Dart:
const env = String.fromEnvironment('ENV', defaultValue: 'dev');
const apiUrl = String.fromEnvironment('API_URL');
Per gestire molte variabili conviene usare un file JSON con --dart-define-from-file=config/dev.json, supportato dalle versioni recenti di Flutter.
Automatizzare con la configurazione dell'IDE
In VS Code definisci le configurazioni in .vscode/launch.json:
{
"configurations": [
{
"name": "Dev",
"request": "launch",
"type": "dart",
"program": "lib/main_dev.dart",
"args": ["--flavor", "dev"]
},
{
"name": "Prod",
"request": "launch",
"type": "dart",
"program": "lib/main_prod.dart",
"args": ["--flavor", "prod"]
}
]
}
Best practice
- Non committare segreti: mantieni chiavi API e credenziali fuori dal repository, usando file di configurazione ignorati o secret manager della CI.
- Un solo punto di verità: centralizza la configurazione in
AppConfiginvece di sparpagliare condizioniif (env == ...)nel codice. - Icone differenti: usa
flutter_launcher_iconscon override per flavor per generare icone diverse e riconoscere subito la build. - Integra la CI/CD: parametrizza i job di build in base al flavor per generare artifact separati per ogni ambiente.
Conclusione
I flavor sono la soluzione standard per gestire ambienti multipli in Flutter. Con una configurazione iniziale ben strutturata — flavor nativi, entry point Dart e una classe AppConfig centralizzata — potrai passare da sviluppo a produzione con un solo comando, riducendo errori e velocizzando il ciclo di rilascio.