一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第18天,点击查看活动详情。
原文链接:
- TypeAdapters (hivedb.dev)
- Generate adapter (hivedb.dev)
- HiveObject (hivedb.dev)
- Relationships (hivedb.dev)
- Create adapter manually (hivedb.dev)
pub: hive | Dart Package (flutter-io.cn)
pub译文: [译]纯Dart键值(对象)数据库hive - 掘金 (juejin.cn)
译时版本: hive 2.1.0
TypeAdapter(类型适配器)
Hive 支持所有的基本类型、 List 、 Map 、 DateTime 和 Uint8List 。如果想要存储其它对象,需要注册一个 TypeAdapter (类型适配器)用于将对象转换为二进制形式。
可以自己写一个 TypeAdapter 或者生成它。大多数情况,生成的适配器实际上表现很好。用手动写的适配器,有时候可以做一些改进。
注册适配器
当想要 Hive 使用 TypeAdapter 时,需要注册它。
需要做两件事情:适配器的实例和一个 typeId 。每个类型都有唯一的 typeId ,当从磁盘取到一个值时,它用来查找正确的适配器。所有的 typeId 允许在 0 到 223 之间。
Hive.registerAdapter(MyObjectAdapter());Copy to clipboardErrorCopied
确保固定地使用
typeId。改动必须和之前版本的 box 兼容。
建议在打开任意 box 之前注册所有的
TypeAdapter。
import 'package:hive/hive.dart';
class User {
String name;
User(this.name);
@override
String toString() => name; // 只用于打印
}
void main() async {
// 注册适配器
Hive.registerAdapter(UserAdapter());
var box = await Hive.openBox<User>('userBox');
box.put('david', User('David'));
box.put('sandy', User('Sandy'));
print(box.values);
}
// 可自动生成
class UserAdapter extends TypeAdapter<User> {
@override
final typeId = 0;
@override
User read(BinaryReader reader) {
return User(reader.read());
}
@override
void write(BinaryWriter writer, User obj) {
writer.write(obj.name);
}
}
生成适配器
hive_generator 包几乎可以为任何类自动生成 TypeAdapter 。
- 要为一个类生成
TypeAdapter,可添加@HiveType注解并提供一个typeId(0 到 223之间) - 用
@HiveField注解所有要存储的字段 - 运行编译任务
flutter packages pub run build_runner build - 注册生成的适配器
示例
这里有一个 person.dart 库,使用带有唯一的 typeId 的 @HiveType 来注解 Person 类:
import 'package:hive/hive.dart';
part 'person.g.dart';
@HiveType(typeId: 1)
class Person {
@HiveField(0)
String name;
@HiveField(1)
int age;
@HiveField(2)
List<Person> friends;
}
正如所看到的,每个 @HiveField 注解的字段都有唯一的编号(每个类中唯一)。这些字段的编号用于标识 Hive 二进制中的字段,并且一旦类被使用,该编号就不应该被修改。
字段编号的范围可为 0~255。
上面的代码生成的适配器类名为 PersonAdapter 。也可以使用 @HiveType 的可选参数 adapterName 改变适配器的类名。
更新一个类
如果一个存在的类需要修改 - 例如,类需要一个新的字段 - 但是你仍然希望读取旧适配器写入的对象,不必担心!不破坏现有代码更新生成的适配器很简单。只需要记住下面的规则:
- 不要改变任何现有字段的字段编号。
- 如果添加新的字段,任何使用『旧』适配器写入的对象仍然可用新适配器读取。这些字段只是会被忽略。类似地,新代码写入的对象可以被旧代码读取:解析时新的字段会被忽略。
- 只要字段编号保持不变,字段可以重命名,甚至可以从 public 改为 private 或者反之。
- 字段可以被移除,只要字段编号在更新的类中不再被使用。
- 不支持改变字段的类型。应该创建一个新字段来代替。
- 空安全可用之后,对于新的非空字段需要提供
defaultValue(默认值)。
枚举
为枚举生成适配器的机制几乎与为类生成适配器的机制相同:
@HiveType(typeId: 2)
enum HairColor {
@HiveField(0)
brown,
@HiveField(1)
blond,
@HiveField(2)
black,
}
上面的(更新)规则同样适用于更新枚举。
默认值
可以通过 @HiveField 注解的 defaultValue 参数为属性和字段提供默认值。
@HiveType(typeId: 2)
class Customer {
@HiveField(1, defaultValue: 0.0)
double balance;
}
用于自定义类型的默认值是在
hive: 2.0.4和hive_generator: 1.1.0之后引入的。
也可以通过将 defaultValue 设置为 true 为枚举类型提供默认值。
如果没有为枚举类型设定默认值,第一个值会被用作默认值。
@HiveType(typeId: 2)
enum HairColor {
@HiveField(0)
brown,
@HiveField(1)
blond,
@HiveField(2, defaultValue: true)
black,
}
HiveObject
当在 Hive 中存储自定义对象时,可以继承 HiveObject 来轻松地管理对象。 HiveObject 提供了对象的键和有用的辅助方法,如 save() 或 delete() 。
这里有一个如何使用 HiveObject 的示例:
import 'package:hive/hive.dart';
void main() async {
Hive.registerAdapter(PersonAdapter());
var persons = await Hive.openBox('persons');
var person = Person()
..name = 'Lisa';
persons.add(person); // 第一次会存储该对象
print('Number of persons: ${persons.length}');
print("Lisa's first key: ${person.key}");
person.name = 'Lucas';
person.save(); // 更新对象
person.delete(); // 从 Hive 中移除对象
print('Number of persons: ${persons.length}');
persons.put('someKey', person);
print("Lisa's second key: ${person.key}");
}
@HiveType()
class Person extends HiveObject {
@HiveField(0)
String name;
}
class PersonAdapter extends TypeAdapter<Person> {
@override
final typeId = 0;
@override
Person read(BinaryReader reader) {
return Person()..name = reader.read();
}
@override
void write(BinaryWriter writer, Person obj) {
writer.write(obj.name);
}
}
如果想要使用查询,也需要继承
HiveObject。
关系
有时,模型会相互关连。下面的 Person 类有一个名为 friends 的其它 Person 的列表。
也可以有其它的对象如 pets 。
class Person extends HiveObject {
String name;
int age;
List<Person> friends;
Person(this.name, this.age);
}
可以用一般的列表来存储这些 Person ,但是更新单个 Person 会相当复杂,因为 Person 对象会是冗余存储。
HiveList
HiveList 提供了解决上述问题的方法。它们允许存储实际对象的 "链接":
import 'package:hive/hive.dart';
void main() async {
Hive.registerAdapter(PersonAdapter());
var persons = await Hive.openBox<Person>('personsWithLists');
persons.clear();
var mario = Person('Mario');
var luna = Person('Luna');
var alex = Person('Alex');
persons.addAll([mario, luna, alex]);
mario.friends = HiveList(persons); // 创建一个 HiveList
mario.friends.addAll([luna, alex]); // 更新 Mario 的 friends
mario.save(); // 将改动持久化
print(mario.friends);
luna.delete(); // 从 Hive 中移除 Luna
print(mario.friends); // HiveList 会自动更新
}
@HiveType()
class Person extends HiveObject {
@HiveField(0)
String name;
@HiveField(1)
HiveList friends;
Person(this.name);
String toString() => name; // 用于打印
}
class PersonAdapter extends TypeAdapter<Person> {
@override
final typeId = 0;
@override
Person read(BinaryReader reader) {
return Person(reader.read())..friends = reader.read();
}
@override
void write(BinaryWriter writer, Person obj) {
writer.write(obj.name);
writer.write(obj.friends);
}
}
首先,我们在 persons box 中存储了三个人,Mario 、 Luna 和 Alex。
接着,我们创建了一个 HiveList ,它包含 Mario 的朋友。HiveList 构造方法需要包含朋友列表的 HiveObject 。 该列表必须不能移动到其它 HiveObject 。第二个参数是 box,包含列表的项目。
当从 box 中删除一个对象时,也会从所有的 HiveLists 中删除。如果从 HiveList 删除一个对象,它仍然会留在 box 中。
手动创建适配器
有时候,创建自定义的 TypeAdapter 很有必要。可以通过继承 TypeAdapter 类实现。要确保指定了通用的参数。
要彻底地测试自定义的
TypeAdapter。如果它没有正确工作,可能会破坏 box 。
实现一个 TypeAdapter 非常简单。记住 TypeAdapter 必须是不可变的!这里有一个在 Hive 内部使用的 DateTimeAdapter :
class DateTimeAdapter extends TypeAdapter<DateTime> {
@override
final typeId = 16;
@override
DateTime read(BinaryReader reader) {
final micros = reader.readInt();
return DateTime.fromMicrosecondsSinceEpoch(micros);
}
@override
void write(BinaryWriter writer, DateTime obj) {
writer.writeInt(obj.microsecondsSinceEpoch);
}
}Copy to clipboardErrorCopied
到 Hive 1.3.0 为止,所有的适配器需要
typeId的实例变量!
typeId 实例变量分配了用于注册适配器的编号。它在所有的适配器中必须是唯一的。当从磁盘读取对象时 read() 方法会被调用。使用 BinaryReader 读取对象的所有属性。在上面的示例中,只是一个包含 microsecondsSinceEpoch 的 int (整数)。
write() 方法也是一样的,只是它用于向磁盘写入对象。
确保按照之前写入属性的相同顺序读取属性。