Flutter网络请求适配多环境方案

在Flutter开发中,实现多环境(开发/测试/生产)的网络请求切换,关键在于配置管理运行时环境识别。以下是三种高效实现方案:


方案一:编译时配置(推荐)

通过--dart-define在编译时注入环境变量

// 环境配置类
enum Env { dev, test, prod }

class AppConfig {
  static final Env _env = _getEnv();
  
  static String get baseUrl {
    switch(_env) {
      case Env.dev: return "https://dev-api.com";
      case Env.test: return "https://test-api.com";
      case Env.prod: return "https://api.com";
    }
  }
  
  static Env _getEnv() {
    const env = String.fromEnvironment('ENV', defaultValue: 'dev');
    switch(env) {
      case 'test': return Env.test;
      case 'prod': return Env.prod;
      default: return Env.dev;
    }
  }
}

// 网络请求使用
final dio = Dio()
  ..options.baseUrl = AppConfig.baseUrl;

运行命令

# 开发环境(默认)
flutter run

# 测试环境
flutter run --dart-define=ENV=test

# 生产环境
flutter run --dart-define=ENV=prod

优点

  • 无运行时开销
  • 配置编译进包,无法篡改
  • 支持CI/CD自动化构建

方案二:配置文件动态加载

使用JSON配置文件按环境加载

  1. 创建配置文件:
// assets/config_dev.json
{"baseUrl": "https://dev-api.com"}

// assets/config_prod.json
{"baseUrl": "https://api.com"}

  1. 配置加载器:
Future<Map<String, dynamic>> loadConfig() async {
  const env = bool.hasEnvironment('ENV') 
      ? const String.fromEnvironment('ENV')
      : 'dev';
      
  final configFile = await rootBundle.loadString(
    'assets/config_${env}.json'
  );
  return json.decode(configFile);
}

// 初始化
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final config = await loadConfig();
  runApp(MyApp(config: config));
}


方案三:运行时环境切换

适用于调试阶段动态切换

// 环境切换器
class EnvSwitcher extends StatelessWidget {
  final Widget child;
  
  const EnvSwitcher({super.key, required this.child});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Builder(
        builder: (context) => child,
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => _showEnvDialog(context),
        child: const Icon(Icons.settings),
      ),
    );
  }

  void _showEnvDialog(BuildContext context) {
    showDialog(
      context: context,
      builder: (_) => AlertDialog(
        title: const Text('切换环境'),
        actions: [
          _buildEnvButton(context, '开发', 'dev'),
          _buildEnvButton(context, '测试', 'test'),
          _buildEnvButton(context, '生产', 'prod'),
        ],
      )
    );
  }

  Widget _buildEnvButton(BuildContext ctx, String text, String env) {
    return TextButton(
      onPressed: () {
        Provider.of<EnvProvider>(ctx, listen: false).setEnv(env);
        Navigator.pop(ctx);
      },
      child: Text(text)
    );
  }
}

// 环境提供者
class EnvProvider with ChangeNotifier {
  String _currentEnv = 'dev';
  
  String get baseUrl {
    switch(_currentEnv) {
      case 'test': return "https://test-api.com";
      case 'prod': return "https://api.com";
      default: return "https://dev-api.com";
    }
  }
  
  void setEnv(String env) {
    _currentEnv = env;
    notifyListeners();
  }
}


最佳实践建议

  1. 安全隔离

    • 生产环境关闭调试日志
    if (AppConfig.env != Env.prod) {
      dio.interceptors.add(LogInterceptor());
    }
    

  2. 证书校验

    dio.httpClientAdapter = IOHttpClientAdapter(
      onHttpClientCreate: (client) {
        if (AppConfig.env == Env.prod) {
          client.badCertificateCallback = 
            (cert, host, port) => host == "api.com";
        }
        return client;
      }
    );
    

  3. 环境标识

    dio.interceptors.add(InterceptorsWrapper(
      onRequest: (options) {
        options.headers['X-Env'] = AppConfig.env.name;
        return options;
      }
    ));
    

  4. 自动化测试

    testWidgets('生产环境配置验证', (tester) async {
      await tester.runAsync(() async {
        const String.fromEnvironment('ENV', 'prod');
        expect(AppConfig.baseUrl, "https://api.com");
      });
    });
    


方案对比

方案 适用场景 安全性 动态切换
编译时配置 正式发布包 ★★★★★
配置文件 需要热更新配置 ★★★☆☆
运行时切换 开发调试阶段 ★★☆☆☆

选择方案时需考虑:

  1. 是否需要上线后修改环境(选方案二)
  2. 是否需要防止用户篡改(选方案一)
  3. 是否频繁调试切换(选方案三)
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐