[官网文档翻译]Flutter持久化库drift - 高级特性 - 类型转换器

728 阅读3分钟

「这是我参与11月更文挑战的第26天,活动详情查看:2021最后一次更文挑战」。

Flutter持久化库drift(原moor)官方文档翻译汇总 - 掘金 (juejin.cn)

本文翻译自 drift 的 官方文档 Type converters (simonbinder.eu)

肉翻多有不足,不吝赐教。


重要通知: moor 已改名为 drift 。更多信息[中文]。

类型转换器

使用类型转换器可以在列中存储复杂的数据。

drift 支持各种各样的类型(开箱即用),但是有时也需要存储复杂些的数据。 可以使用 TypeConverter (类型转换器)来实现这一点。在这个示例中,将会使用 json_serializable 包在一个 text 列中存储一个自定义对象。 drift 对提供的 TypeConverter 支持任意 Dart 类型,在这里我们使用这个包来简化示例。

在 Dart 中使用转换器

import 'dart:convert';

import 'package:json_annotation/json_annotation.dart' as j;
import 'package:drift/drift.dart';

part 'database.g.dart';

@j.JsonSerializable()
class Preferences {
  bool receiveEmails;
  String selectedTheme;

  Preferences(this.receiveEmails, this.selectedTheme);

  factory Preferences.fromJson(Map<String, dynamic> json) =>
      _$PreferencesFromJson(json);

  Map<String, dynamic> toJson() => _$PreferencesToJson(this);
}

接下来,需要告诉 drift 如何在数据库中存储一个 Preferneces 对象。为此编写了 TypeConverter (类型转换器):

// 将 preferences 存储为 字符串
class PreferenceConverter extends TypeConverter<Preferences, String> {
  const PreferenceConverter();
  @override
  Preferences? mapToDart(String? fromDb) {
    if (fromDb == null) {
      return null;
    }
    return Preferences.fromJson(json.decode(fromDb) as Map<String, dynamic>);
  }

  @override
  String? mapToSql(Preferences? value) {
    if (value == null) {
      return null;
    }

    return json.encode(value.toJson());
  }
}

最后,在表声明中使用这个转换器。

class Users extends Table {
  IntColumn get id => integer().autoIncrement()();
  TextColumn get name => text()();

  TextColumn get preferences =>
      text().map(const PreferenceConverter()).nullable()();
}

生成的 User 类会有一个 preferences 列,类型为 Preferences。 drift 会自动负责在 selectupdateinsert 语句中存储和加载对象。[编译过的自定义查询] (drift.simonbinder.eu/queries/cus…) 同样适用这个特性。

相等比较的警告事项

如果转换器返回了一个不能用值进行比较的对象,生成的数据类也不能用值进行比较。

隐式枚举转换器

一个类型转换器的常见情景是:将枚举表现为下标这样在枚举和整数之间进行映射。因为这个很常见,所以 drift 集成了 intEnum 列类型来简化映射:

enum Status { 
   none, 
   running, 
   stopped, 
   paused 
}

class Tasks extends Table {
  IntColumn get id => integer().autoIncrement()();
  IntColumn get status => intEnum<Status>()();
}

枚举的警告事项 引入另外的枚举值容易意外地使数据库作废。例如:比方说在上面的示例中向数据库中插入一个 Task ,然后把它的 status 设置为 running (下标等于 1 )。现在 Status 枚举中包含了一个新的成员。

  enum Status { 
    none, 
    starting, // new!
    running, 
    stopped, 
    paused 
  }

当选择 task (任务)时,现在它会报告为 starting ,因为在下标 1 的位置有了新的值。由于这个原因,最好的方式是在枚举的最后添加新的值,这样就不会和现有的值冲突。否则就需要升级 shema 版本,然后运行一个自定义更新语句来修复。

也要注意不能在声明为枚举转换器的列上应用另外一个类型转换器。

在 drift 文件中使用转换器

类型转换器也可以在 drift 文件内部使用。假设 PreferencesPreferenceConverter 包含在 preferences.dart中,这个文件可以引入到 drift 文件中,使类型转换器可用。

import 'preferences.dart';

CREATE TABLE users (
  id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
  name TEXT,
  preferences TEXT MAPPED BY `const PreferenceConverter()`
);

在 drift 文件中使用类型转换器时,建议设定 apply_converters_on_variables 构建选项。这样在 Dart 转为 sql 时也会应用转换器。例如:如果对 SELECT * FROM users WHERE preferences = ? 的变量使用了这个选项,变量会被当作 Preferences 而不是 String

drift 文件对隐式枚举转换器也有专门的支持:

import 'status.dart';

CREATE TABLE tasks (
  id INTEGER NOT NULL PRIMARY KEY,
  status ENUM(Status)
);

当然,自动枚举转换器的相关警告也适用于 drift 文件。