When building production-grade applications, hardcoding API endpoints, feature flags, or tracking keys directly into your source code is an architectural anti-pattern. This guide establishes a compile-time, zero-overhead environment orchestration system leveraging strict encapsulation patterns and modern features in native Dart.
Table of Contents
1. Define the Immutable Flavor Domain
📂 lib/flavor/flavor_enum.dart
library;
enum Flavor {
development,
staging,
production;
static Flavor fromString(String? value) {
return Flavor.values.firstWhere(
(e) => e.name == value?.toLowerCase(),
orElse: () => Flavor.production,
);
}
}
2. Abstract the Target Build Context
⚙️ lib/flavor/build_mode/build_mode.dart
import 'package:flutter/foundation.dart';
enum BuildMode {
debug,
profile,
release;
static BuildMode get current {
if (kDebugMode) return BuildMode.debug;
if (kProfileMode) return BuildMode.profile;
if (kReleaseMode) return BuildMode.release;
throw UnimplementedError('Active environment build mode is unrecognized.');
}
}
3. Architect the Config Interface
⚙️ lib/flavor/config/config.dart
part of '../flavor.dart';
abstract interface class FlavorConfig {
const FlavorConfig._(this.baseUrl, this.flavor);
final String baseUrl;
final Flavor flavor;
factory FlavorConfig({String? flavorName = appFlavor}) {
final flavor = Flavor.fromString(flavorName);
switch (flavor) {
case Flavor.development:
return const _DevCfg();
case Flavor.staging:
return const _StgCfg();
case Flavor.production:
return const _ProdCfg();
}
}
BuildMode get buildMode => BuildMode.current;
}
4. Implement Isolated Concrete Subtypes
📄 Concrete Configurations
// lib/flavor/config/dev/dev_cfg.dart
part of '../../flavor.dart';
class _DevCfg extends FlavorConfig {
const _DevCfg() : super._('https://api.dev.yourdomain.com', Flavor.development);
}
// lib/flavor/config/stg/stg_cfg.dart
part of '../../flavor.dart';
class _StgCfg extends FlavorConfig {
const _StgCfg() : super._('https://api.stg.yourdomain.com', Flavor.staging);
}
// lib/flavor/config/prod/prod_cfg.dart
part of '../../flavor.dart';
class _ProdCfg extends FlavorConfig {
const _ProdCfg() : super._('https://api.prod.yourdomain.com', Flavor.production);
}
5. Consolidate the Public Library Entrypoint
📦 lib/flavor/flavor.dart
library;
export 'package:flutter/services.dart' show appFlavor;
import 'build_mode/build_mode.dart' show BuildMode;
import 'flavor_enum.dart' show Flavor;
part 'config/config.dart';
part 'config/dev/dev_cfg.dart';
part 'config/stg/stg_cfg.dart';
part 'config/prod/prod_cfg.dart';
6. Automating Build Infrastructure
🤖 android/app/build.gradle
android {
flavorDimensions += "default"
productFlavors {
create("development") {
dimension = "default"
applicationIdSuffix = ".dev"
resValue("string", "app_name", "MyApp DEV")
}
create("staging") {
dimension = "default"
applicationIdSuffix = ".stg"
resValue("string", "app_name", "MyApp STAGING")
}
create("production") {
dimension = "default"
resValue("string", "app_name", "MyApp")
}
}
}