不管在什么样的应用中,如果要处理大量的数据,不可避免的就是要定义大量的数据类用来装载和解析数据,在 Flutter 中也不例外,今天要介绍的这个 Freezed 库就是 Flutter 中用来作为数据类(data classes)代码生成的这样一款工具。

freezed 是什么

freezed 是一个 Flutter/Dart 生态系统中一个非常强大的代码生成工具,用于创建数据类,基于 Dart 的代码生成功能,通过自动生成 data classes, tagged unions, nested classes 和 clone 代码模板,大大减少了手动编写重复性代码的工作量。freezed 的设计非常类似 Java 生态中的 [[Lombok]],通过注解和代码生成来减少样板代码量。

尽管 Dart 很棒,但是在定义 Model 的时候还是非常乏味,编程者需要

  • 定义构造函数,属性
  • 重载 toString, == hashCode 等
  • 实现 copyWith 方法来 clone 对象
  • 处理序列化以及反序列化等

如果要实现这一些,一个 Model 可能就需要上百行代码,这不仅容易出错,而且还影响了代码易读性。freezed 就是设计用来来帮助开发者只需要关注定义 Model,而无需考虑其他。

比如一个简单的用户定义

class Person with _$Person {
  Person({
    required this.firstName;
    required this.lastName;
    required this.age;
  })
  final String firstName;
  final String lastName;
  final String age;
}

freezed 安装

为了使用 Freezed,需要依赖 build_runner 代码生成,首先安装 build_runner 和 Freezed,添加依赖到 pubspec.yaml 中。

通过命令行添加

flutter pub add freezed_annotation
flutter pub add dev:build_runner
flutter pub add dev:freezed
# if using freezed to generate fromJson/toJson, also add:
flutter pub add json_annotation
flutter pub add dev:json_serializable

直接修改 pubspec.yaml 文件

dependencies:
  flutter:
    sdk: flutter
  freezed_annotation: ^3.0.4
  json_annotation: ^4.0.6  # 如需JSON序列化支持

dev_dependencies:
  build_runner: ^2.3.2
  freezed: ^3.0.4
  json_serializable: ^6.5.4  # 如需JSON序列化支持

然后运行 flutter pub get

说明

  • build_runner 用来运行代码生成
  • freezed 是代码生成器
  • freezed_annotation,包含了 freezed 注解

运行代码生成

dart run build_runner watch -d
# or
flutter pub run build_runner build --delete-conflicting-outputs

和其他代码生成器需要的一样,freezed 需要导入 freezed 注解,并且需要使用 part 关键字

import 'package:freezed_annotation/freezed_annotation.dart';

part 'my_file.freezed.dart';

freezed 使用

为了避免与 JSON 序列化相关的警告,建议在项目根目录的analysis_options.yaml文件中添加以下配置

analyzer:
  errors:
    invalid_annotation_target: ignore

创建 Model

Freezed 提供两种方式来创建 data-classes

  • Primary constructors,定义构造函数,Freezed 生成关联字段
  • Classic classes,编写正常的 Dart 类,Freezed 只生成 toString/ == / copyWith

Primary constructors

Freezed 实现 Primary Constructors 通过 factory 关键字。

import 'package:freezed_annotation/freezed_annotation.dart';

// required: associates our `main.dart` with the code generated by Freezed
part 'main.freezed.dart';
// optional: Since our Person class is serializable, we must add this line.
// But if Person was not serializable, we could skip it.
part 'main.g.dart';

@freezed
abstract class Person with _$Person {
  const factory Person({
    required String firstName,
    required String lastName,
    required int age,
  }) = _Person;

  factory Person.fromJson(Map<String, Object?> json) => _$PersonFromJson(json);
}

定义了 Person

  • 属性
  • 使用了 @freezed 注解,类属性是不可变的
  • 定义了 fromJson,序列化和反序列化,Freezed 会添加 toJson 方法
  • Freezed 会自动生成 toString / == / hashCode / copyWith

执行命令

flutter pub run build_runner build --delete-conflicting-outputs

命令将生成 xxx.freezed.dart 文件,以及 xxx.g.dart 包含序列化相关的代码

如果有一些情况需要定义 getters 或者自定义方法,这个时候需要定义一个空的构造函数

@freezed
abstract class Person with _$Person {
  // Added constructor. Must not have any parameter
  const Person._();

  const factory Person(String name, {int? age}) = _Person;

  void method() {
    print('hello world');
  }
}

定义可变类

需要使用 @unfreezed

@unfreezed
abstract class Person with _$Person {
  factory Person({
    required String firstName,
    required String lastName,
    required final int age,
  }) = _Person;

  factory Person.fromJson(Map<String, Object?> json) => _$PersonFromJson(json);
}

姓名是可变的

void main() {
  var person = Person(firstName: 'John', lastName: 'Smith', age: 42);

  person.firstName = 'Mona';
  person.lastName = 'Lisa';
}

并且不会实现 == / hashCode 方法,所以下面的比较等于 false

void main() {
  var john = Person(firstName: 'John', lastName: 'Smith', age: 42);
  var john2 = Person(firstName: 'John', lastName: 'Smith', age: 42);

  print(john == john2); // false
}

让 Lists/Maps/Sets 可变

通常情况下,如果使用 @freezed ,那么内部属性如果使用 List/Map/Set 会自动变成不可变

@freezed
abstract class Example with _$Example {
  factory Example(List<int> list) = _Example;
}

void main() {
  var example = Example([]);
  example.list.add(42); // throws because we are mutating a collection
}

需要调整为

@Freezed(makeCollectionsUnmodifiable: false)
abstract class Example with _$Example {
  factory Example(List<int> list) = _Example;
}

void main() {
  var example = Example([]);
  example.list.add(42); // OK
}

Classic classes

和之前提到的 Primary constructors 相比,这个模式下,我们只需要定义普通的 Dart classes。

通常编写 constructor 和 fields 定义。

import 'package:freezed_annotation/freezed_annotation.dart';

// required: associates our `main.dart` with the code generated by Freezed
part 'main.freezed.dart';
// optional: Since our Person class is serializable, we must add this line.
// But if Person was not serializable, we could skip it.
part 'main.g.dart';

@freezed
@JsonSerializable()
class Person with _$Person {
  const Person({
    required this.firstName,
    required this.lastName,
    required this.age,
  });

  final String firstName;
  final String lastName;
  final int age;

  factory Person.fromJson(Map<String, Object?> json)
      => _$PersonFromJson(json);

  Map<String, Object?> toJson() => _$PersonToJson(this);
}

Freezed 会自动处理 copyWith / toString / == / hashCode 。

  • DartJ 是一个可以将 JSON 快速转变成 Dart 类定义的工具。