Gestire ambienti multipli in Flutter con i flavor (dev, staging, prod)
Best practiceIntermedio40 min Flutter 3.x

Gestire ambienti multipli in Flutter con i flavor (dev, staging, prod)

Gestire ambienti multipli con i flavor

Quando 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.

In 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/Configuration). Useremo anche --dart-define per passare valori sensibili senza scriverli nel codice.

Al termine avrai un progetto in grado di essere compilato con flutter run --flavor dev senza toccare il codice sorgente.

  1. 1

    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.).

    Centralizzare la configurazione qui rende il resto dell'app agnostico rispetto all'ambiente: tutti leggeranno da AppConfig.instance.

    // lib/config/app_config.dart
    enum Flavor { dev, staging, prod }
    
    class AppConfig {
      final Flavor flavor;
      final String appName;
      final String apiBaseUrl;
    
      static late AppConfig instance;
    
      AppConfig._({
        required this.flavor,
        required this.appName,
        required this.apiBaseUrl,
      });
    
      factory AppConfig.create({
        required Flavor flavor,
        required String appName,
        required String apiBaseUrl,
      }) {
        instance = AppConfig._(
          flavor: flavor,
          appName: appName,
          apiBaseUrl: apiBaseUrl,
        );
        return instance;
      }
    
      bool get isProd => flavor == Flavor.prod;
    }

    Risultato atteso

    Una classe AppConfig riutilizzabile in tutta l'app, con un'istanza singleton inizializzata all'avvio.

  2. 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).

    Questo approccio è chiaro e verificabile: chi legge il codice capisce subito quali sono le differenze tra gli ambienti.

    // lib/main_common.dart
    import 'package:flutter/material.dart';
    import 'config/app_config.dart';
    import 'app.dart';
    
    void mainCommon() {
      runApp(const MyApp());
    }
    
    // lib/main_dev.dart
    void main() {
      AppConfig.create(
        flavor: Flavor.dev,
        appName: 'MyApp DEV',
        apiBaseUrl: 'https://dev.api.example.com',
      );
      mainCommon();
    }
    
    // lib/main_prod.dart
    void main() {
      AppConfig.create(
        flavor: Flavor.prod,
        appName: 'MyApp',
        apiBaseUrl: 'https://api.example.com',
      );
      mainCommon();
    }

    Risultato atteso

    Tre entry point (main_dev.dart, main_staging.dart, main_prod.dart) che condividono la stessa app ma con configurazioni diverse.

  3. 3

    Configurare i flavor su Android con Gradle

    Su Android i flavor si definiscono nel blocco android del file android/app/build.gradle. Definiamo una flavorDimensions e i tre productFlavors, ognuno con un applicationIdSuffix diverso in modo da poter installare più build affiancate.

    Il resValue sovrascrive il nome dell'app mostrato sotto l'icona.

    // android/app/build.gradle
    android {
        flavorDimensions "app"
    
        productFlavors {
            dev {
                dimension "app"
                applicationIdSuffix ".dev"
                resValue "string", "app_name", "MyApp DEV"
            }
            staging {
                dimension "app"
                applicationIdSuffix ".stg"
                resValue "string", "app_name", "MyApp STG"
            }
            prod {
                dimension "app"
                resValue "string", "app_name", "MyApp"
            }
        }
    }

    Risultato atteso

    Android riconosce i tre flavor; il nome dell'app varia in base al flavor selezionato.

  4. 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.

    <!-- android/app/src/main/AndroidManifest.xml -->
    <application
        android:label="@string/app_name"
        android:icon="@mipmap/ic_launcher">
        <!-- ... -->
    </application>

    Risultato atteso

    L'app installata mostra il nome corretto in base al flavor (es. "MyApp DEV").

  5. 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/Runner.xcworkspace):

    1. In Project > Runner > Info, duplica le configurazioni Debug e Release per ottenere Debug-dev, Release-dev, Debug-prod, ecc.
    2. Vai su Product > Scheme > Manage Schemes e crea uno scheme per ogni flavor (dev, staging, prod), associando le configurazioni corrispondenti.
    3. Per cambiare il nome dell'app, aggiungi una User-Defined Setting chiamata APP_DISPLAY_NAME per ciascuna configurazione e usala nel file Info.plist.

    Nel Info.plist referenzia la variabile:

    <!-- ios/Runner/Info.plist -->
    <key>CFBundleDisplayName</key>
    <string>$(APP_DISPLAY_NAME)</string>
    <key>CFBundleName</key>
    <string>$(APP_DISPLAY_NAME)</string>

    Risultato atteso

    Xcode dispone di uno scheme per ogni flavor e il nome dell'app dipende dalla configurazione selezionata.

  6. 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.

    Suggerimento: 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
    flutter run --flavor dev -t lib/main_dev.dart
    
    # Con variabili d'ambiente extra
    flutter run --flavor prod -t lib/main_prod.dart \
      --dart-define=API_KEY=abc123
    
    # Build di produzione (APK)
    flutter build apk --flavor prod -t lib/main_prod.dart --release
    
    # Build iOS
    flutter build ios --flavor prod -t lib/main_prod.dart --release

    Risultato atteso

    L'app si compila e si avvia con la configurazione dell'ambiente selezionato, installabile in parallelo agli altri flavor.

  7. 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.

    // lib/app.dart
    import 'package:flutter/material.dart';
    import 'config/app_config.dart';
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
    
      static const apiKey = String.fromEnvironment('API_KEY', defaultValue: '');
    
      @override
      Widget build(BuildContext context) {
        final config = AppConfig.instance;
        return MaterialApp(
          title: config.appName,
          debugShowCheckedModeBanner: !config.isProd,
          home: Scaffold(
            appBar: AppBar(title: Text(config.appName)),
            body: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Text('Ambiente: ${config.flavor.name}'),
                  Text('API: ${config.apiBaseUrl}'),
                ],
              ),
            ),
          ),
        );
      }
    }

    Risultato atteso

    L'app visualizza il nome, l'ambiente e l'URL base corretti, con il banner di debug attivo solo fuori dalla produzione.

CondividiXLinkedInFacebookWhatsApp

Commenti (0)

Ancora nessun commento. Inizia tu!