[{"data":1,"prerenderedAt":78},["ShallowReactive",2],{"tutorial-gestire-ambienti-multipli-in-flutter-con-i-flavor-dev-staging-prod":3,"comments-tutorial-gestire-ambienti-multipli-in-flutter-con-i-flavor-dev-staging-prod":77},{"id":4,"title":5,"slug":6,"excerpt":7,"intro":8,"cover_image":9,"video_url":10,"difficulty":11,"estimated_minutes":12,"flutter_version":13,"status":14,"published_at":15,"meta_title":16,"meta_description":17,"category":18,"author":22,"steps":25},34,"Gestire ambienti multipli in Flutter con i flavor (dev, staging, prod)","gestire-ambienti-multipli-in-flutter-con-i-flavor-dev-staging-prod","Impara a configurare flavor separati in Flutter per Android e iOS, gestendo variabili d'ambiente, nomi dell'app e configurazioni diverse per sviluppo, staging e produzione.","# Gestire ambienti multipli con i flavor\n\nQuando si sviluppa un'app professionale è quasi sempre necessario avere più ambienti: uno per lo **sviluppo** (dev), uno per il collaudo (**staging**) e uno per la **produzione** (prod). Ogni ambiente punta tipicamente a URL di backend diversi, ha chiavi API distinte e magari un'icona o un nome differente per poterli installare contemporaneamente sullo stesso dispositivo.\n\nIn questo tutorial vedremo come implementare i **flavor** in Flutter, configurandoli sia lato Dart (con gli *entry point* e le variabili d'ambiente) sia lato nativo (Android Gradle e iOS Scheme\u002FConfiguration). Useremo anche `--dart-define` per passare valori sensibili senza scriverli nel codice.\n\n> Al termine avrai un progetto in grado di essere compilato con `flutter run --flavor dev` senza toccare il codice sorgente.","https:\u002F\u002Fflutter.it\u002Fstorage\u002Ftutorials\u002Ff46203a0-06ca-4361-87b7-a96d1ce44c5e.jpg",null,"intermediate",40,"3.x","published","2026-07-10T04:30:53+00:00","Flavor in Flutter: gestire dev, staging e prod","Guida pratica alla configurazione dei flavor in Flutter per Android e iOS: ambienti multipli, dart-define e entry point separati.",{"id":19,"name":20,"slug":21},3,"Best practice","best-practice",{"id":23,"name":24},1,"Flutter Bot",[26,33,40,47,55,62,70],{"id":27,"position":23,"title":28,"body":29,"code_snippet":30,"code_language":31,"expected_result":32,"demo_url":10,"video_url":10},230,"Definire la configurazione dell'ambiente in Dart","Iniziamo creando una classe che rappresenta la configurazione di un ambiente. Useremo un `enum` per identificare il flavor e una classe `AppConfig` che espone i valori (nome app, URL base, ecc.).\n\nCentralizzare la configurazione qui rende il resto dell'app agnostico rispetto all'ambiente: tutti leggeranno da `AppConfig.instance`.","\u002F\u002F lib\u002Fconfig\u002Fapp_config.dart\nenum Flavor { dev, staging, prod }\n\nclass AppConfig {\n  final Flavor flavor;\n  final String appName;\n  final String apiBaseUrl;\n\n  static late AppConfig instance;\n\n  AppConfig._({\n    required this.flavor,\n    required this.appName,\n    required this.apiBaseUrl,\n  });\n\n  factory AppConfig.create({\n    required Flavor flavor,\n    required String appName,\n    required String apiBaseUrl,\n  }) {\n    instance = AppConfig._(\n      flavor: flavor,\n      appName: appName,\n      apiBaseUrl: apiBaseUrl,\n    );\n    return instance;\n  }\n\n  bool get isProd => flavor == Flavor.prod;\n}","dart","Una classe AppConfig riutilizzabile in tutta l'app, con un'istanza singleton inizializzata all'avvio.",{"id":34,"position":35,"title":36,"body":37,"code_snippet":38,"code_language":31,"expected_result":39,"demo_url":10,"video_url":10},231,2,"Creare gli entry point per ogni flavor","Invece di un solo `main.dart`, creiamo un file di ingresso per ciascun ambiente. Ogni file inizializza `AppConfig` con i valori corretti e poi avvia lo stesso widget radice condiviso (`app.dart`).\n\nQuesto approccio è chiaro e verificabile: chi legge il codice capisce subito quali sono le differenze tra gli ambienti.","\u002F\u002F lib\u002Fmain_common.dart\nimport 'package:flutter\u002Fmaterial.dart';\nimport 'config\u002Fapp_config.dart';\nimport 'app.dart';\n\nvoid mainCommon() {\n  runApp(const MyApp());\n}\n\n\u002F\u002F lib\u002Fmain_dev.dart\nvoid main() {\n  AppConfig.create(\n    flavor: Flavor.dev,\n    appName: 'MyApp DEV',\n    apiBaseUrl: 'https:\u002F\u002Fdev.api.example.com',\n  );\n  mainCommon();\n}\n\n\u002F\u002F lib\u002Fmain_prod.dart\nvoid main() {\n  AppConfig.create(\n    flavor: Flavor.prod,\n    appName: 'MyApp',\n    apiBaseUrl: 'https:\u002F\u002Fapi.example.com',\n  );\n  mainCommon();\n}","Tre entry point (main_dev.dart, main_staging.dart, main_prod.dart) che condividono la stessa app ma con configurazioni diverse.",{"id":41,"position":19,"title":42,"body":43,"code_snippet":44,"code_language":45,"expected_result":46,"demo_url":10,"video_url":10},232,"Configurare i flavor su Android con Gradle","Su Android i flavor si definiscono nel blocco `android` del file `android\u002Fapp\u002Fbuild.gradle`. Definiamo una `flavorDimensions` e i tre `productFlavors`, ognuno con un `applicationIdSuffix` diverso in modo da poter installare più build affiancate.\n\nIl `resValue` sovrascrive il nome dell'app mostrato sotto l'icona.","\u002F\u002F android\u002Fapp\u002Fbuild.gradle\nandroid {\n    flavorDimensions \"app\"\n\n    productFlavors {\n        dev {\n            dimension \"app\"\n            applicationIdSuffix \".dev\"\n            resValue \"string\", \"app_name\", \"MyApp DEV\"\n        }\n        staging {\n            dimension \"app\"\n            applicationIdSuffix \".stg\"\n            resValue \"string\", \"app_name\", \"MyApp STG\"\n        }\n        prod {\n            dimension \"app\"\n            resValue \"string\", \"app_name\", \"MyApp\"\n        }\n    }\n}","gradle","Android riconosce i tre flavor; il nome dell'app varia in base al flavor selezionato.",{"id":48,"position":49,"title":50,"body":51,"code_snippet":52,"code_language":53,"expected_result":54,"demo_url":10,"video_url":10},233,4,"Collegare il nome app al Manifest Android","Perché il `resValue app_name` abbia effetto, dobbiamo referenziarlo nell'`AndroidManifest.xml`. Sostituisci l'attributo `android:label` con il riferimento alla risorsa stringa che abbiamo definito in Gradle.","\u003C!-- android\u002Fapp\u002Fsrc\u002Fmain\u002FAndroidManifest.xml -->\n\u003Capplication\n    android:label=\"@string\u002Fapp_name\"\n    android:icon=\"@mipmap\u002Fic_launcher\">\n    \u003C!-- ... -->\n\u003C\u002Fapplication>","xml","L'app installata mostra il nome corretto in base al flavor (es. \"MyApp DEV\").",{"id":56,"position":57,"title":58,"body":59,"code_snippet":60,"code_language":53,"expected_result":61,"demo_url":10,"video_url":10},234,5,"Configurare i flavor su iOS con gli Scheme","Su iOS il concetto equivalente sono le **Build Configuration** e gli **Scheme**. Apri il progetto con Xcode (`open ios\u002FRunner.xcworkspace`):\n\n1. In *Project > Runner > Info*, duplica le configurazioni `Debug` e `Release` per ottenere `Debug-dev`, `Release-dev`, `Debug-prod`, ecc.\n2. Vai su *Product > Scheme > Manage Schemes* e crea uno scheme per ogni flavor (dev, staging, prod), associando le configurazioni corrispondenti.\n3. Per cambiare il nome dell'app, aggiungi una *User-Defined Setting* chiamata `APP_DISPLAY_NAME` per ciascuna configurazione e usala nel file `Info.plist`.\n\nNel `Info.plist` referenzia la variabile:","\u003C!-- ios\u002FRunner\u002FInfo.plist -->\n\u003Ckey>CFBundleDisplayName\u003C\u002Fkey>\n\u003Cstring>$(APP_DISPLAY_NAME)\u003C\u002Fstring>\n\u003Ckey>CFBundleName\u003C\u002Fkey>\n\u003Cstring>$(APP_DISPLAY_NAME)\u003C\u002Fstring>","Xcode dispone di uno scheme per ogni flavor e il nome dell'app dipende dalla configurazione selezionata.",{"id":63,"position":64,"title":65,"body":66,"code_snippet":67,"code_language":68,"expected_result":69,"demo_url":10,"video_url":10},235,6,"Eseguire e compilare specificando il flavor","Ora possiamo lanciare l'app scegliendo flavor ed entry point con il flag `--flavor` e `-t` (target). Puoi anche passare valori sensibili con `--dart-define` per non hardcodarli.\n\nSuggerimento: salva questi comandi nei *launch.json* di VS Code o nelle configurazioni di Android Studio per averli sempre a portata di mano.","# Esecuzione in sviluppo\nflutter run --flavor dev -t lib\u002Fmain_dev.dart\n\n# Con variabili d'ambiente extra\nflutter run --flavor prod -t lib\u002Fmain_prod.dart \\\n  --dart-define=API_KEY=abc123\n\n# Build di produzione (APK)\nflutter build apk --flavor prod -t lib\u002Fmain_prod.dart --release\n\n# Build iOS\nflutter build ios --flavor prod -t lib\u002Fmain_prod.dart --release","bash","L'app si compila e si avvia con la configurazione dell'ambiente selezionato, installabile in parallelo agli altri flavor.",{"id":71,"position":72,"title":73,"body":74,"code_snippet":75,"code_language":31,"expected_result":76,"demo_url":10,"video_url":10},236,7,"Usare la configurazione nell'app e leggere i dart-define","Infine, mostriamo come consumare `AppConfig` all'interno dei widget e come leggere i valori passati con `--dart-define` tramite `String.fromEnvironment`. Questo ci permette, ad esempio, di mostrare un banner di debug solo negli ambienti non di produzione.","\u002F\u002F lib\u002Fapp.dart\nimport 'package:flutter\u002Fmaterial.dart';\nimport 'config\u002Fapp_config.dart';\n\nclass MyApp extends StatelessWidget {\n  const MyApp({super.key});\n\n  static const apiKey = String.fromEnvironment('API_KEY', defaultValue: '');\n\n  @override\n  Widget build(BuildContext context) {\n    final config = AppConfig.instance;\n    return MaterialApp(\n      title: config.appName,\n      debugShowCheckedModeBanner: !config.isProd,\n      home: Scaffold(\n        appBar: AppBar(title: Text(config.appName)),\n        body: Center(\n          child: Column(\n            mainAxisAlignment: MainAxisAlignment.center,\n            children: [\n              Text('Ambiente: ${config.flavor.name}'),\n              Text('API: ${config.apiBaseUrl}'),\n            ],\n          ),\n        ),\n      ),\n    );\n  }\n}","L'app visualizza il nome, l'ambiente e l'URL base corretti, con il banner di debug attivo solo fuori dalla produzione.",[],1785219608870]