精选文章

Flutter 功能开关与远程配置

2021-02-19 · 组件

功能开关(Feature Flags)是“稳定性”和“迭代速度”的折中方案。本文给出一套可直接复用的代码范式:本地默认值、远程配置、缓存与回滚。

1. 定义统一 Flag 列表

enum FeatureFlag {
  newProfile,
  chatV2,
  experimentalFeed,
}

2. 本地默认值

const defaultFlags = {
  FeatureFlag.newProfile: false,
  FeatureFlag.chatV2: true,
  FeatureFlag.experimentalFeed: false,
};

3. 统一管理器

class FeatureFlags {
  final Map<FeatureFlag, bool> _flags;
  FeatureFlags(this._flags);

  bool isEnabled(FeatureFlag flag) => _flags[flag] ?? false;

  static Future<FeatureFlags> load() async {
    // 先加载本地默认值
    final flags = Map<FeatureFlag, bool>.from(defaultFlags);
    // 再加载远程配置
    final remote = await RemoteConfig.fetch();
    flags.addAll(remote);
    return FeatureFlags(flags);
  }
}

4. 远程配置(示例)

class RemoteConfig {
  static Future<Map<FeatureFlag, bool>> fetch() async {
    // TODO: 用真实 API 替换
    await Future.delayed(const Duration(milliseconds: 200));
    return {
      FeatureFlag.experimentalFeed: true,
    };
  }
}

5. 缓存与回滚

远程配置应该缓存,以便离线可用:

class FlagCache {
  static Future<void> save(Map<FeatureFlag, bool> flags) async {
    final prefs = await SharedPreferences.getInstance();
    final map = flags.map((k, v) => MapEntry(k.name, v));
    await prefs.setString('flags', jsonEncode(map));
  }

  static Future<Map<FeatureFlag, bool>> load() async {
    final prefs = await SharedPreferences.getInstance();
    final raw = prefs.getString('flags');
    if (raw == null) return {};
    final map = jsonDecode(raw) as Map<String, dynamic>;
    return map.map((k, v) => MapEntry(FeatureFlag.values.byName(k), v as bool));
  }
}

如果远程失败,就使用缓存或默认值。

6. 使用方式

final flags = await FeatureFlags.load();
if (flags.isEnabled(FeatureFlag.newProfile)) {
  showNewProfile();
} else {
  showOldProfile();
}

7. 实践清单

  • Flag 列表统一定义
  • 默认值 + 远程配置合并
  • 缓存兜底
  • 远程失败可回滚

总结

功能开关不是“开关按钮”,而是一个可靠的工程能力:默认值、远程下发、缓存回滚缺一不可。

JJ

作者简介

专注于内容创作、产品策略与设计实践。欢迎交流合作。

下一篇