Mobiludvikling med Flutter · Lektion

Build-flavors og miljøkonfiguration

Definér dev-, staging- og prod-flavors med assets pr. miljø og Dart-define-konfigurationer.

Lektion 1 af 413 trin

Build-flavors og miljøkonfiguration er en gratis Mobiludvikling med Flutter-lektion på CoddyKit. Dette er lektion 1 af 4. Du kan læse alle 3 lektioner i dette læringsspor gratis i deres fulde længde — derefter låser CoddyKit PRO alle lektioner op samt praktiske øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. Den er en del af læringsforløbet i Mobiludvikling med Flutter, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. Mobiludvikling med Flutter-kurset indeholder 4 lektioner i alt.

Hvorfor build-varianter er vigtige

En Flutter-produktionsapp kommunikerer sjældent med én enkelt backend. Du har brug for et dev-build, der peger på en lokal API eller en staging-API, et staging-build til kvalitetssikring og et hærdet prod-build til appbutikken.

  • Flavors er navngivne build-varianter, der kan have forskelligt app-id, ikon, navn og signering.
  • Miljøkonfiguration er de data, som hver flavor injicerer: basis-URL'er, feature flags og API-nøgler.

Målet er at installere dev, staging og prod side om side på én enhed, hver fuldstændigt isoleret og umulig at forveksle.

De to lag: native flavors og Dart-define

En ren opsætning adskiller to ansvarsområder:

  • Native flavors (Android productFlavors, iOS schemes/xcconfig) styrer applikationens identitet: bundle-id, visningsnavn og ikon.
  • Dart-define-værdier styrer konfiguration under kørsel, som din Dart-kode læser: API-URL, miljønavn og logniveau.

Native flavors lader dev, staging og prod eksistere side om side på enheden med forskellige id'er som com.acme.app.dev. Dart-define holder hemmeligheder og URL'er ude af kildekoden og fastlåser dem til en bestemt version på kompileringstidspunktet.

Definition af en typesikker miljø-enum

Begynd i Dart med én sandhedskilde for, hvilke miljøer der findes. En enum forhindrer slåfejl og muliggør udtømmende håndtering med switch.

Dette kodestykke er ren Dart uden Flutter-afhængigheder, så det kan køre overalt.

enum Environment { dev, staging, prod }

String labelFor(Environment env) {
  switch (env) {
    case Environment.dev:
      return 'Development';
    case Environment.staging:
      return 'Staging';
    case Environment.prod:
      return 'Production';
  }
}

void main() {
  for (final env in Environment.values) {
    print('${env.name} -> ${labelFor(env)}');
  }
}

Læsning af Dart-define på kompileringstidspunktet

Flutter stiller konstanter til rådighed på kompileringstidspunktet via String.fromEnvironment, bool.fromEnvironment og int.fromEnvironment. Du angiver dem med --dart-define på buildtidspunktet.

  • Værdierne indlejres i binærfilen; de læses IKKE fra enheden under kørsel.
  • Angiv altid en defaultValue, så et manglende define giver en forudsigelig fejl.

Da disse er const, kan de evalueres selv i const-kontekster.

const String apiUrl = String.fromEnvironment(
  'API_URL',
  defaultValue: 'http://localhost:8080',
);

const String envName = String.fromEnvironment(
  'ENV',
  defaultValue: 'dev',
);

const bool analyticsEnabled = bool.fromEnvironment(
  'ANALYTICS',
  defaultValue: false,
);

void main() {
  print('env=$envName url=$apiUrl analytics=$analyticsEnabled');
}

Opbygning af et AppConfig-objekt

Hvis du spreder kald til String.fromEnvironment ud over din kodebase, mister du kontrollen. Saml dem i ét uforanderligt AppConfig, som du løser én gang og sender videre.

Det holder alle forskelle mellem flavors samlet ét sted, der kan revideres, og gør testning enkel: Du opretter blot et AppConfig med de ønskede værdier.

class AppConfig {
  final String envName;
  final String apiUrl;
  final bool analyticsEnabled;

  const AppConfig({
    required this.envName,
    required this.apiUrl,
    required this.analyticsEnabled,
  });

  factory AppConfig.fromEnvironment() {
    return const AppConfig(
      envName: String.fromEnvironment('ENV', defaultValue: 'dev'),
      apiUrl: String.fromEnvironment('API_URL',
          defaultValue: 'http://localhost:8080'),
      analyticsEnabled:
          bool.fromEnvironment('ANALYTICS', defaultValue: false),
    );
  }

  bool get isProd => envName == 'prod';
}

void main() {
  final config = AppConfig.fromEnvironment();
  print('Running in ${config.envName} -> ${config.apiUrl}');
}

Videregivelse af Dart-define på kommandolinjen

Du injicerer konfiguration, når du kører eller bygger. Hvert --dart-define angiver én nøgle. Kombinér det med den native flavor via --flavor.

  • --flavor staging vælger den native variant (id, ikon og navn).
  • --dart-define leverer værdier til dit AppConfig.

Det er fejlbehæftet at skrive disse hver gang, så teams gemmer dem i scripts eller filer (i næste scene).

flutter run \
  --flavor staging \
  --target lib/main.dart \
  --dart-define=ENV=staging \
  --dart-define=API_URL=https://staging.api.acme.com \
  --dart-define=ANALYTICS=true

flutter build apk \
  --release \
  --flavor prod \
  --dart-define=ENV=prod \
  --dart-define=API_URL=https://api.acme.com \
  --dart-define=ANALYTICS=true

Dart-define-from-file til renere CI

Lange kæder af --dart-define er skrøbelige. Flutter understøtter --dart-define-from-file, som læser en JSON-fil eller en fil i .env-stil med nøgle-værdi-par.

  • Hav én fil pr. miljø: config/dev.json, config/staging.json og config/prod.json.
  • Commit filer uden hemmeligheder, og injicer filer med hemmeligheder i CI fra et sikkert lager.

Et eksempel på config/prod.json og den tilhørende kørsel vises. Nøglerne svarer én til én til dine fromEnvironment-opslag.

// config/prod.json
{
  "ENV": "prod",
  "API_URL": "https://api.acme.com",
  "ANALYTICS": true
}

// Invocation:
// flutter build appbundle --release \
//   --flavor prod \
//   --dart-define-from-file=config/prod.json

Android: productFlavors

På Android erklærer du flavors i android/app/build.gradle. Hver flavor tilsidesætter suffikset til applikations-id'et og navnet, så builds kan installeres side om side.

  • applicationIdSuffix føjer noget til basis-id'et (f.eks. com.acme.app.dev).
  • resValue tilsidesætter launcherens etiket for hver flavor.

Der kræves en flavorDimensions-post, før flavors listes.

android {
    flavorDimensions "env"
    productFlavors {
        dev {
            dimension "env"
            applicationIdSuffix ".dev"
            resValue "string", "app_name", "Acme Dev"
        }
        staging {
            dimension "env"
            applicationIdSuffix ".staging"
            resValue "string", "app_name", "Acme Staging"
        }
        prod {
            dimension "env"
            resValue "string", "app_name", "Acme"
        }
    }
}

iOS: Schemes og xcconfig

På iOS svarer flavors til Xcode-schemes, der bygger på build-konfigurationer og .xcconfig-filer. Hvert scheme angiver et særskilt PRODUCT_BUNDLE_IDENTIFIER og visningsnavn.

  • Opret konfigurationer som Debug-dev, Release-prod osv.
  • En .xcconfig pr. miljø tilsidesætter bundle-id'et og DISPLAY_NAME, som læses i Info.plist via $(DISPLAY_NAME).

Flutter matcher --flavor prod med Xcode-scheme'et, der hedder prod.

// ios/Flutter/staging.xcconfig
#include "Generated.xcconfig"
PRODUCT_BUNDLE_IDENTIFIER = com.acme.app.staging
DISPLAY_NAME = Acme Staging

// In Info.plist:
// <key>CFBundleDisplayName</key>
// <string>$(DISPLAY_NAME)</string>

Assets pr. miljø

Flavors har ofte brug for forskellige assets: et farvet DEV-banner, et appikon til staging eller en anden Firebase-konfiguration.

  • Organisér assets i mapper som assets/dev/ og assets/prod/, og vælg stien under kørsel fra dit AppConfig.
  • For native ikoner finder Android automatisk src/dev/res; iOS bruger asset-kataloger pr. konfiguration.
  • Placér flavor-specifikke google-services.json-filer under android/app/src/<flavor>/.

Dart-siden udleder blot asset-stien fra det aktive miljø.

class AssetPaths {
  final String envName;
  const AssetPaths(this.envName);

  String get logo => 'assets/$envName/logo.png';
  String get configBanner =>
      envName == 'prod' ? '' : 'assets/$envName/banner.png';
}

void main() {
  for (final env in ['dev', 'staging', 'prod']) {
    final paths = AssetPaths(env);
    print('$env logo: ${paths.logo}');
  }
}

Sådan integrerer du AppConfig i main()

Løs konfigurationen én gang ved opstart, og gør den tilgængelig for widget-træet (via en InheritedWidget, provider eller service locator). Undgå at læse fromEnvironment dybt inde i dine widgets.

  • Opbyg konfigurationen før runApp.
  • Vis et synligt miljøbanner for builds, der ikke er prod, så kvalitetssikring ikke forveksler miljøerne.

Kodestykket nedenfor er Flutter-frameworkkode, så det kan ikke køres alene, men viser det kanoniske indgangspunkt.

import 'package:flutter/material.dart';

void main() {
  final config = AppConfig.fromEnvironment();
  runApp(MyApp(config: config));
}

class MyApp extends StatelessWidget {
  final AppConfig config;
  const MyApp({super.key, required this.config});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Acme (${config.envName})',
      debugShowCheckedModeBanner: !config.isProd,
      home: const Scaffold(body: Center(child: Text('Home'))),
    );
  }
}

Hurtigt tjek: Hvor konfigurationen ligger

Du har konfigureret dev-, staging- og prod-flavors. Test din forståelse af, hvordan Dart-define-værdier fungerer.

Opsummering

Du har opbygget en komplet strategi for flavor- og miljøkonfiguration:

  • To lag: Native flavors (Android productFlavors, iOS schemes/xcconfig) angiver appens identitet; dart-define angiver konfiguration under kørsel.
  • Typesikkerhed: én Environment-enum og et uforanderligt AppConfig, der løses én gang via AppConfig.fromEnvironment().
  • Injektion: Angiv --flavor sammen med --dart-define, eller skalér på en ren måde med --dart-define-from-file=config/<env>.json.
  • Assets: Mapper pr. miljø og native ressource-tilsidesættelser til ikoner, bannere og Firebase-konfigurationer.
  • Vigtig indsigt: dart-define evalueres på kompileringstidspunktet og indlejres i binærfilen, så ændring af konfiguration altid kræver et nyt build.

Gevinsten er, at dev, staging og prod installeres side om side, er fuldstændigt isolerede og umulige at forveksle.

Gratis at komme i gang

Lær Dart med en AI-underviser — gratis

Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.

Kurser
22
Lektioner
88

Ofte stillede spørgsmål

Er lektionen “Build-flavors og miljøkonfiguration” gratis?

Ja — alle 3 lektioner i læringssporet Mobiludvikling med Flutter, inklusive “Build-flavors og miljøkonfiguration”, kan læses gratis i deres fulde længde her på webstedet. Derefter låser CoddyKit PRO alle lektioner op samt interaktive øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. Mobiludvikling med Flutter-kurset indeholder 4 lektioner i alt.

Hvad lærer jeg i “Build-flavors og miljøkonfiguration”?

Definér dev-, staging- og prod-flavors med assets pr. miljø og Dart-define-konfigurationer. Du øver dig i Mobiludvikling med Flutter med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.

Skal jeg have erfaring for at begynde på Mobiludvikling med Flutter?

Der kræves ingen tidligere erfaring. Mobiludvikling med Flutter på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 1 af 4.

Hvor lang tid tager lektionen “Build-flavors og miljøkonfiguration”?

De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.

Kan jeg skrive og køre kode i denne Mobiludvikling med Flutter-lektion?

Ja. Alle Mobiludvikling med Flutter-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.

Alle lektioner i dette kursus

  1. Build-flavors og miljøkonfiguration
  2. Automatiserede pipelines med Fastlane og GitHub Actions
  3. Crashrapportering og symboliserede stack traces
  4. Remote config, feature flags og trinvise udrulninger
← Tilbage til Mobiludvikling med Flutter