速查 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: false | null 时不写入 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 只扫描必要目录 |
相关文档
- 09 · 数据层链路 —— 模型与 Retrofit / Dio 的配合
- 08 · 状态管理 Riverpod —— 状态里的模型
- 速查 02 · Dart —— 语法糖、集合操作、异步 API
- 踩坑 · Flutter 构建与打包 ——
build_runner相关构建错误