一起养成写作习惯!这是我参与「日新计划 4 月更文挑战」的第17天,点击查看活动详情。


原文链接:

  • What are boxes? (hivedb.dev)
  • Read & Write (hivedb.dev)
  • Hive in Flutter (hivedb.dev)
  • Auto increment & indices (hivedb.dev)

pub:
hive | Dart Package (flutter-io.cn)

pub译文:
[译]纯Dart键值(对象)数据库hive – ()

译时版本:
hive 2.1.0


box 是什么?

在 Hive 中,所有的数据都被组织到 box 中。box 可以比作 SQL 中的表,但是它没有结构,可以包含任何内容。

对于小的应用,单个 box 差不多够用。对于更复杂的问题,box 是极好的组织数据的方式。box 也可以加密用来存储敏感数据。

打开 box

在能够使用 box 之前,必须先打开它。对于常规的 box ,这会从本地存储加载所有数据到内存中用于即时访问。

var box = await Hive.openBox<E>('testBox');
参数 说明
name box 的名称,它指定了存储位置,和检查一个 box 是否存在。 大小写敏感
encryptionKey 该键必须是32位长度的字节数组,用来加密和解密 box 中的所有值。
keyComparator 默认情况下,键使用词典编纂排序。该参数允许提供一个自定义排序。
compactionStrategy 指定自动压缩的规则。
crashRecovery 如果应用在写操作运行时被杀死了,最后的实体可能会被破坏。该实体会在应用重启用自动删除。如果不需要这种行为,可以指定为不可用。
path 默认情况下,box 存储在 Hive.init() 指定的目录中。使用该参数,可以指定 box 应该存储的位置。
bytes 代替使用文件作为后台,可以提供二进制形式的 box 并打开一个内存中的 box 。
E 可选的类型参数指定了 box 中值的类型。

如果 box 已经打开,它会立即作为结果返回,然后所有指定的参数都会忽略。

一旦你获得了一个 box 实例,就可以读、写和删除实体了。

获取打开的 box

Hive 存储了所有打开的 box 的引用。
如果想要获取一个打开的 box ,可以使用:

var box = Hive.box('myBox');

该方法对于 Flutter 应用特别有用,因为不需要在组件间传递 box 。

关闭 box

如果不再需要某个 box ,可以关闭它。所有缓存的 box 的键值会从内存中清掉,然后 box 文件会在所有活动的读写操作完成之后关闭。

在应用的运行时保持一个 box 处于打开的状态是非常好的做法。如果将来再次需要 box ,只需要使其保持打开的状态。

var box = await Hive.openBox('myBox');
await box.put('hello', 'world');
await box.close();

在应用存在之前,应用调用Hive.close()关闭所有打开的 box 。不必担心关闭 Hive 之前应用会被杀掉,这没有关系。

类型参数:Box<E>

当打开一个 box 时,可以指定它可能只包含一个指定类型的值。例如,用户的 box 应该如下打开:

var box = await Hive.openBox<User>('users');
box.add(User());
box.add(5); // Compile time error* (编译时错误)

该 box 也可以包含 User 的子类型。

Hive.box() 指定同样的类型参数很重要。不能用不同的类型参数多次打开相同的 box 。

await Hive.openBox<User>('users');
Hive.box<User>('users'); // OK
Hive.box('users'); // ERROR
Hive.box<Dog>('users'); // ERROR

由于 Dart 的限制,不支持泛型参数如 Box<List> 。


读写

从 box 中进行读操作非常简单:

var box = Hive.box('myBox');
String name = box.get('name');
DateTime birthday = box.get('birthday');

如果键不存在,会返回 null。也可以选择指定defaultValue(默认值),用于 key 不存在时返回。

double height = box.get('randomKey', defaultValue: 17.5);

get()返回的列表永远是 List<dynamic>Map<dynamic, dynamic>类型的 Map )类型。可使用list.cast<SomeType>()转换为指定的类型。

每个对象只存在一次

用指定的键值永远都会返回对象的同一个实例。对于基本类型没有影响,因为基本类型是不可变的,但是对于其它对象是必要的。

这里有个示例:

??

官网的示例代码显示不出来呢?

initialListmyList是同一个实例,共享相同的数据。

在上面的示例中,只有缓存的对象被改变,而不是底层数据。要持久化该改变,需要调用 box.put('myList', myList)

向 box 中写内容就像是向 Map 中写内容。所有的键都必须是 ASCII 字符串,用最大长度为 255 字符或者使用无符号的32位整数。

var box = Hive.box('myBox');
box.put('name', 'Paul');
box.put('friends', ['Dave', 'Simon', 'Lisa']);
box.put(123, 'test');
box.putAll({'key1': 'value1', 42: 'life'});

你可能想知道写工作为什么不需要异步代码的原因。这是 Hive 的主要优点之一。
改动会尽快在后台写入到磁盘上,但是所有的监听器都会被即时通知。如果异步操作失败了(当然不应该出现),会使用旧的值再次通知所有的监听器。
如果想要确保写操作成功,只需要 await 其Future

该运转机制和lazy box 不同:
只要 put() 返回的 Future 没有完成,get() 都会返回旧的值(不存在的时候返回 null)。

下面的代码展示了差异:

var box = await Hive.openBox('box');
box.put('key', 'value');
print(box.get('key')); //  值
var lazyBox = await Hive.openLazyBox('lazyBox');
var future = lazyBox.put('key', 'value');
print(lazyBox.get('key')); // null
await future;
print(lazyBox.get('key')); // 值

删除

如果想要改变现有的值,可以覆写它,如使用 put() 或删除它:

???

官网的示例代码显示不出来呢?

如果键不存在,则无需访问磁盘,然后返回的 Future 会立刻完成。

写入 null 和 删除

写入null和删除值是不同的

import 'package:hive/hive.dart';
void main() async {
  var box = await Hive.openBox('writeNullBox');
  box.put('key', 'value');
  box.put('key', null);
  print(box.containsKey('key'));
  box.delete('key');
  print(box.containsKey('key'));
}

在Flutter中使用Hive

Hive 支持所有 Flutter 支持的平台。

初始化 Flutter 应用

在能够打开 box 之前,Hive 需要知道可以存储它的数据的位置。
对于允许访问的目录,Android 和 iOS 有非常严格的规则。可以使用path_provider包获取一个有效的目录。

hive_flutter包提供了Hive.initFlutter()扩展方法,该方法会处理所有事情。

可监听的值

如果希望组件基于 Hive 中存储的值进行刷新,可以使用ValueListenableBuilderbox.listenable()方法提供了ValueListenable ,它也可以和 provider 包一起使用。

import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';
void main() async {
  await Hive.initFlutter();
  await Hive.openBox('settings');
  runApp(MyApp());
}
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Demo Settings',
      home: Scaffold(
        body: ValueListenableBuilder<Box>(
          valueListenable: Hive.box('settings').listenable(),
          builder: (context, box, widget) {
            return Center(
              child: Switch(
                value: box.get('darkMode', defaultValue: false),
                onChanged: (val) {
                  box.put('darkMode', val);
                },
              ),
            );
          },
        ),
      ),
    );
  }
}

每次 darkMode 关联的值改变时,builder都会被调用,并且Switch组件会刷新。

监听指定的键

只在必要时刷新组件是比较好的实践。如果一个组件只依赖 box 中的指定键,可以提供 keys参数:

ValueListenableBuilder<Box>(
  valueListenable: Hive.box('settings').listenable(keys: ['firstKey', 'secondKey']),
  builder: (context, box, widget) {
    // build widget
  },
)

异步调用

一些 Hive 的方法如put()delete()是异步的。处理异步调用并不是很有趣,因为必须使用FutureBuilderStreamBuilder并处理异常。

好消息是不需要 await 所有 Hive 返回的 Future 。发生改变时会即时反映,即使Future还未完成。如果想要确保一个写操作成功,只需要 await 其Future


自增和下标

我们已经知道 Hive 支持无符号的键。如果你喜欢的话,可以使用自增的键。在排序和访问多个对象时会非常有用。可以像 List 一样使用 Box 。

import 'package:hive/hive.dart';
void main() async {
  var friends = await Hive.openBox('friends');
  friends.clear();
  friends.add('Lisa');            // index 0, key 0
  friends.add('Dave');            // index 1, key 1
  friends.put(123, 'Marco');      // index 2, key 123
  friends.add('Paul');            // index 3, key 124
  print(friends.getAt(0));
  print(friends.get(0));
  print(friends.getAt(1));
  print(friends.get(1));
  print(friends.getAt(2));
  print(friends.get(123));
  print(friends.getAt(3));
  print(friends.get(124));
}

也有getAt()putAt()deleteAt()方法使用它们的下标访问或改变值。

默认情况下,字符串键以词典编纂方式排序,它们也有下标。

即使只使用自增的键,也不应该依赖键和下标是相同的。