Stop Guessing: A Scalable Strategy for Flutter Environment Management

mg3994

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.

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")
        }
    }
}

Post a Comment