Skip to content

速查 08 · Freezed 与 build_runner ​

按问题索引。Ctrl+F 页内搜索。 模型在项目中的用法见 09 · 数据层链路。

本项目版本:Freezed 2.5(配合 json_serializable)。

目录 ​


最小模型模板 ​

dart
import 'package:freezed_annotation/freezed_annotation.dart';

part 'user.freezed.dart';      // ⚠️ 两行 part **缺一不可**
part 'user.g.dart';

@freezed
class User with _$User {
  const factory User({
    required String id,
    String? name,
    @Default(0) int age,
  }) = _User;

  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}

四个易漏点:

漏了会怎样必须写
生成文件不生效part 'x.freezed.dart'; + part 'x.g.dart';
_$UserFromJson 找不到factory User.fromJson(...) => _$UserFromJson(json);
类名对不上class User with _$User 与 = _User 的命名规则
构造函数必须是 const factory

联合类型与 when / map ​

dart
@freezed
class ResultState<T> with _$ResultState<T> {
  const factory ResultState.idle() = Idle;
  const factory ResultState.loading() = Loading;
  const factory ResultState.success(T data) = Success<T>;
  const factory ResultState.failure(String message) = Failure;
}

// 使用:when **必须穷尽所有分支**
state.when(
  idle:    () => const SizedBox.shrink(),
  loading: () => const CircularProgressIndicator(),
  success: (data) => Text(data.toString()),
  failure: (msg) => Text(msg),
);

// 只处理部分
state.maybeWhen(
  loading: () => const CircularProgressIndicator(),
  orElse:  () => const SizedBox.shrink(),
);

// 只要一个分支的值
final msg = state.maybeWhen(failure: (m) => m, orElse: () => null);
方法特点
when必须穷尽,新增分支时编译报错(安全)
maybeWhen用 orElse 兜底,新增分支不会报错(注意漏改)
map / maybeMap同 when,但回调拿到的是整个类型对象而非解构后的值

🎯 状态机类用 when(穷尽=安全);UI 里只想处理一种用 maybeWhen。


默认值与字段重命名 ​

dart
@freezed
class User with _$User {
  const factory User({
    required String id,
    @Default('匿名') String name,                        // 默认值
    @Default(<String>[]) List<String> tags,             // 空集合
    @JsonKey(name: 'created_at') DateTime? createdAt,   // 后端 snake_case
    @JsonKey(includeIfNull: false) String? remark,       // 为 null 时不输出
    @JsonKey(ignore: true) @Default(false) bool local,   // 不参与序列化
  }) = _User;
}

常用 @JsonKey:

参数作用
name:字段重命名(snake_case ↔ camelCase)
includeIfNull: falsenull 时不写入 JSON
ignore: true完全不参与序列化
fromJson: / toJson:自定义转换(如时间戳转 DateTime)
unknownEnumValue:枚举兜底值(强烈建议加,防后端新增枚举导致解析崩)

copyWith 与「置 null」 ​

dart
final u2 = u1.copyWith(name: '李四');   // ✅ 普通修改

// Freezed 生成的是"可置 null"的 copyWith
final u3 = u1.copyWith(name: null);     // ✅ 把 name 置回 null

⚠️ 若字段带了 @Default(...),copyWith(field: null) 会回到默认值而不是 null—— 想置 null 就不要给该字段加 @Default。


嵌套对象与 explicitToJson ​

dart
@freezed
class Order with _$Order {
  const factory Order({
    required String id,
    required User user,          // 嵌套的另外一个 freezed 类
  }) = _Order;

  factory Order.fromJson(Map<String, dynamic> json) => _$OrderFromJson(json);
}

必须开启 explicitToJson,否则嵌套对象序列化会报 Converting object to an encodable object failed:

dart
// 加在根类上
@Freezed(fromJson: false)   // 或
@JsonSerializable(explicitToJson: true)   // ✅ 推荐这一行
@freezed
class Order with _$Order { ... }

或全局配置 build.yaml:

yaml
targets:
  $default:
    builders:
      json_serializable:
        options:
          explicit_to_json: true    # ✅ 一劳永逸

build_runner 命令速查 ​

bash
# 一次性生成(**最常用**)
fvm flutter pub run build_runner build --delete-conflicting-outputs

# 监听改动自动生成(开发中开着)
fvm flutter pub run build_runner watch --delete-conflicting-outputs

# 彻底重来(解决诡异的生成问题)
fvm flutter pub run build_runner clean
fvm flutter pub run build_runner build --delete-conflicting-outputs
场景用哪个
改完模型,要生成build --delete-conflicting-outputs
持续开发watch
生成结果不对/有残留先 clean 再 build

💡 团队协作:生成文件(.freezed.dart / .g.dart)是否提交? 两种流派都可,但要统一——不提交就必须在 CI 里跑一次 build_runner,否则打包失败。


常见报错速查 ​

报错 / 现象原因解法
Converting object to an encodable object failed嵌套对象没开 explicitToJson加 @JsonSerializable(explicitToJson: true) 或配 build.yaml
Conflicting outputs were detected上次生成的残留冲突加 --delete-conflicting-outputs,或先 clean
_$XFromJson 未定义漏了 part 'x.g.dart'; 或 factory X.fromJson补齐两处
Bad state: Unexpected diagnostics 之类生成过程被打断/版本不匹配clean 后重跑;检查 freezed 与 flutter 版本兼容
改了模型但生成文件没更新watch 没跑 / 缓存重跑 build --delete-conflicting-outputs
type 'Null' is not a subtype of ...字段标了 required 但后端没返回改成可空 + @Default,或与后端确认字段
枚举解析崩(后端加了新值)没有兜底加 @JsonKey(unknownEnumValue: Xxx.unknown)
生成很慢文件多用 watch;确认 build.yaml 只扫描必要目录

相关文档 ​