Flutter 多环境设计最佳实践:从混乱切换到工程化管理
Flutter 多环境设计最佳实践:从混乱切换到工程化管理
在实际 Flutter 项目中,几乎都会遇到多环境问题:
- 开发环境(dev)
- 测试环境(staging / test)
- 生产环境(prod)
环境差异通常包括:
- 接口地址不同
- 日志等级不同
- 功能开关不同
- 第三方服务 key 不同
很多人一开始是这样做的:
const baseUrl = "http://test-api.xxx.com";
发版前手动改成:
const baseUrl = "https://api.xxx.com";
这种方式看似简单,但存在严重问题:
- 容易误发测试接口到生产
- 每次发版都要改代码
- 无法自动化 CI/CD
- 无法规范团队协作
那么 Flutter 项目中,正确的多环境设计方式是什么?
本文将给出一套工程化解决方案。
一、环境设计的核心思想
Flutter 多环境设计的本质不是“切换地址”,而是:
将环境控制权从代码中剥离,交给构建流程。
完整逻辑可以拆成三层:
构建层 → 决定环境
配置层 → 存储差异
访问层 → 统一管理
二、推荐方案:单入口 + dart-define
很多文章会推荐使用多个 main.dart 或 Android 原生 flavor。
但如果你只是需要:
- 切换接口
- 切换 debug 开关
- 切换日志等级
完全没必要增加原生复杂度。
更推荐:
单入口 + dart-define + JSON 配置文件
三、完整实现步骤
1️⃣ 创建环境配置文件
assets/config/env/
├── dev.json
├── staging.json
└── prod.json
示例:
dev.json
{
"baseUrl": "http://localhost:3000",
"timeout": 10000,
"debug": true
}
prod.json
{
"baseUrl": "https://api.xxx.com",
"timeout": 8000,
"debug": false
}
2️⃣ 注册 assets
在 pubspec.yaml 中:
flutter:
assets:
- assets/config/env/dev.json
- assets/config/env/staging.json
- assets/config/env/prod.json
3️⃣ 定义配置模型
class EnvConfig {
final String baseUrl;
final int timeout;
final bool debug;
EnvConfig({
required this.baseUrl,
required this.timeout,
required this.debug,
});
factory EnvConfig.fromJson(Map<String, dynamic> json) {
return EnvConfig(
baseUrl: json['baseUrl'],
timeout: json['timeout'],
debug: json['debug'],
);
}
}
4️⃣ 创建环境管理类
import 'dart:convert';
import 'package:flutter/services.dart';
import 'env_config.dart';
class Env {
static late EnvConfig _config;
static Future<void> init() async {
const String flavor =
String.fromEnvironment('FLAVOR', defaultValue: 'dev');
final path = 'assets/config/env/$flavor.json';
final jsonStr = await rootBundle.loadString(path);
final jsonMap = json.decode(jsonStr);
_config = EnvConfig.fromJson(jsonMap);
}
static String get baseUrl => _config.baseUrl;
static int get timeout => _config.timeout;
static bool get debug => _config.debug;
}
5️⃣ 在 main.dart 初始化
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Env.init();
runApp(const MyApp());
}
6️⃣ 构建时切换环境
开发环境:
flutter run --dart-define=FLAVOR=dev
测试环境:
flutter run --dart-define=FLAVOR=staging
生产环境:
flutter build apk --dart-define=FLAVOR=prod
四、为什么推荐 dart-define?
String.fromEnvironment() 是:
编译期常量
意味着:
- 构建时写死
- 运行时不能修改
- 无额外性能损耗
- 非运行时判断
这是一种工程化设计,而不是业务层 if 判断。
五、方案对比
| 方案 | 单入口 + dart-define | 多 main + 原生 flavor |
|---|---|---|
| 维护成本 | 低 | 高 |
| 原生配置 | 不需要 | 需要 |
| CI/CD | 简单 | 复杂 |
| 多包名支持 | 不支持 | 支持 |
| 多图标支持 | 不支持 | 支持 |
结论:
- 只切接口 → 用 dart-define
- 多包名多图标 → 用原生 flavor
六、工程化原则总结
- 环境 ≠ 业务逻辑
- 环境差异文件化
- 构建期决定环境
- 运行期只读取配置
- 统一 Env 管理访问
七、安全提醒
不要在 JSON 中存储:
- 私钥
- 支付密钥
- 第三方 secret
Flutter 包是可反编译的。
八、进阶思考
当你理解了这套设计后,可以继续演进:
- 与 CI/CD 集成自动构建
- 支持灰度发布
- 支持远程动态配置
- 与 Android flavor 结合实现多包名
结语
Flutter 多环境设计的核心不是切换地址,
而是:
将环境控制权从代码转移到构建流程。
当你开始从“写代码”转向“设计工程结构”,
你的思维层级就已经发生变化。