由于floor 不支持 Web 平台,所以就需要寻找在Web
平台上的数据库替代方案。然后发现了 Hive,它是一个开源的 Flutter 数据库框架,它支持在全平台上运行且性能很好。虽然不是关系型数据库,只是轻量级的键值对数据库,也能满足一些常用的功能。
安装 1 2 3 4 5 dependencies: flutter: sdk: flutter hive: ^2.2.3 hive_flutter: ^1.1.0
然后运行flutter pub get
就能把Hive
集成到项目中。
架构组件
Box:主要有Box
和LazyBox
两种,区别就是LazyBox
不会把数据库的values
全部加载到内存,而是在需要时才去本地数据库查询。对于Web
平台每一个box
都对应一个IndexedDB
的database
,其他平台的话box
就是一个存放在给定目录的文件。
BoxCollections:支持同时打开或关闭一组普通Box
的集合而且在 Web 平台上IndexedDB
存储数据更高效。提供了对事务的支持且在Web
平台上更高效。
Object:就对应数据库中的Entity
(实体),Hive 可以存储绝大多数的数据类型,例如:Box.add('Dog')
、Box.put('name', 'Jack')
,如果需要存储复杂的数据,就需要自定义一个对象,通常对象需要继承自HiveObject
。
TypeAdapter:是自定义对象的适配器,需要实现 typeId(全局唯一且范围(0,223))属性、read() 和 write() 方法。
支持自定义类型 自定义对象的支持主要有两种方式:
1.实现TypeAdapter
然后在openBox
之前注册 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import 'package:hive/hive.dart' ;class User { String name; User(this .name); } 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.registerAdapter(UserAdapter()); var box = await Hive.openBox<User>('userBox' );box.put('david' , User('Jack' ));
2.使用注解自动生成 TypeAdapter
@HiveType 标注自定义类并提供一个 typeId
属性
@HiveField 标注自定义类的属性,提供一个范围(0,225)的 index 且每个属性的 index 在当前类唯一
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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; }
然后运行dart run build_runner build
就能自动生成 PersonAdapter 了。 使用这种方式,在更新Person
类时不要更改字段的@HiveField 里的 index 和字段的类型,新增字段只要添加@HiveField 新的 index 即可,除非确认字段不再使用才能从类中删除该字段。如果一个字段不为空,在启用空安全后要提供字段的默认值。
3.HiveObject 的作用 当您在 Hive 中存储自定义对象时,您可以继承 HiveObject 来轻松管理您的对象。HiveObject 提供对象的typeId
和有用的辅助方法,如 save() 或 delete()。这时候@HiveType 注解就不需要提供 typeId
属性了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 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(); 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); } }
实际运用 1.首先是实体类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import 'package:hive/hive.dart' ;part 'person.g.dart' ;class Person { final String id; final String name; final String nickName; DateTime? dateCreated; DateTime? dateModified; final bool isFriend; Person(this .id, this .name, this .nickName, this .dateCreated, this .dateModified, this .isFriend); factory Person.fromJson(Map <String , dynamic > json) => _$PersonFromJson(json); Map <String , dynamic > toJson() => _$PersonToJson(this ); }
这里的自定义对象我没有使用注解的方式,而是直接使用了自定义的 TypeAdapter:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import 'package:hive/hive.dart' ;class NullablePersonAdapter extends TypeAdapter <Person ?> { @override int get typeId => 0 ; @override Person? read(BinaryReader reader) { String personString = reader.readString(); Map <String , dynamic >? personJson = personString.isNotEmpty ? json.decode(personString) : null ; return personJson != null ? Person.fromJson(personJson) : null ; } @override void write(BinaryWriter writer, Person? obj) { String? personString = obj != null ? json.encode(obj.toJson()) : null ; writer.writeString(personJson ?? '' ); } }
2.其次是 Box 实现类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 import 'package:hive/hive.dart' ;abstract class PersonBox { static String boxName = 'persons' ; Future<void > savePerson(Person person); Future<Person?> findPersonById(String personId); void closeBox(); } @Injectable (as : PersonBox)class PersonBoxImpl extends PersonBox { Box<Person>? _cachedBox; final LocalStorage _localStorage; PersonBoxImpl(this ._localStorage); @override Future<void > savePerson(Person person) async { Box<Person> innerBox = await _getCachedBox(); await innerBox.put(person.id, person); } @override Future<Person?> findPersonById(String personId) async { Box<Person> innerBox = await _getCachedBox(); return Future.value(innerBox.get (personId)); } @override void closeBox() { _cachedBox?.close(); _cachedBox = null ; } Future<Box<Person>> _getCachedBox() async { return _cachedBox ??= await Hive.openBox<Person>(PersonBox.boxName, collection: _localStorage.userId); } }
3.最后是 HiveManager 聚合类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import 'package:hive/hive.dart' ;class HiveManager { static String hiveBoxName = 'hive-database' ; static Future<HiveManager> initInstance(PersonBox personBox) async { _registerClassAdapter(); await Hive.initFlutter(); HiveManager hiveManager = HiveManager(personBox); return Future.value(hiveManager); } static void _registerClassAdapter() { Hive.registerAdapter<Person?>(NullablePersonAdapter()); } final PersonBox _personBox; const HiveManager(this ._personBox); PersonBox get personBox => _personBox; void closeBox() { personBox.closeBox(); } }
目前遇到的问题
目前开发只在 Web 平台上运行,其他平台还没有试错。
Hive.openBox('name', collection: 'collectionName')
如果打开多个Box
使用同一个collectionName
,除了第一个 Box 会打开成功并创建IndexedDB
的database
,后面的打开的Box
并不会创建database
,这就导致后续Box
在操作数据的Future
方法永远不会有返回值,程序运行到到方法里仿佛一直在等待一样。
官方提供了BoxCollections
来打开多个Box
的集合,但是使用collection
来openBox
的话,虽然会在同一个collectionName
下创建多个 IndexedDB
的database
,但是用这些CollectionBox
操作数据库会报错。