🚅

Flutter 向け Crazy fast な データベース Isar が便利

に公開

Flutter アプリにおいて、端末ローカル内にデータを永続化するための方法はいくつかあります。

Awarefy ではこれまで、SharedPreference または Hive を利用していたのですが、今回アプリに「タグ」のインクリメンタルサーチを実装するための機構を検討していたところ、Isar というパッケージを見つけ採用することにしました。

https://pub.dev/packages/isar

使い方

スキーマの定義

まずはじめにスキーマを定義します。

tag_schema.dart
import 'package:isar/isar.dart';

part 'tag.g.dart';


class Tag {
  Tag({
    required this.uuid,
    required this.label,
    required this.createdAt,
    required this.updatedAt,
  });

  final Id? id;

  // original id of an entity
  (unique: true, replace: true, type: IndexType.value)
  final String uuid;

  (type: IndexType.value)
  final String label;

  final DateTime createdAt;

  final DateTime updatedAt;

}

コード生成

スキーマの定義をしたら、コード生成を行います。

flutter pub run build_runner build

データベース操作処理の実装

データベースの操作処理を実装します。

tag_cache_store.dart
Future<List<domain.Tag>> findByLabel(String text) async {
  final isar = await getDb();
  final rows = await isar.tags
        .filter()
        .labelContains(text, caseSensitive: false)
        .findAll();

  // <<省略 ドメインオブジェクトへの変換処理>>

  return tags;
}

特に気に入った点は、コード生成していることにより、スキーマにマッチしたメソッド、上記の例で言う labelContains() あらかじめ準備されており、Type Safe な開発体験を得られることです。

タグを uuid の値に基づき削除したい場合は次のようになります。

Future<void> remove(String id) async {
  final isar = await getDb();

  await isar.writeTxn(() async {
    await isar.tags.deleteByUuid(id);
  });
}

deleteByUuid() というメソッドが利用できます。ちなみに isar.tagstags も自動生成により作成された部分で、直感的かつ Type Safe にコレクションを操作できます。

Tips

  • Id は 大人しく Nullable にしておく。

Isar では プライマリキーとして Id型 のフィールド id が採用されます。

class Tag {
  Tag({
  }) : id = Isar.autoIncrement;
 
  final Id id;

とかやりたくなるのですが、データ取得したときの挙動が期待通りにならなかったので nullable にして null のママにしておいたほうがよさそうです(詳細未検証)。

  • プライマリキーを任意の型にしたい場合

公式ドキュメントの次のページに記載があるのですが、

https://isar.dev/recipes/string_ids.html

例えば文字列の UUID を持つフィールドの値から int の Hash を生成する関数を用意して Id get isarId => fastHash(id!); のように利用する方法があるようです。

個人的な通常のDB設計でプライマリキーを AutoIncrement な値にすることはせず UUID などを採用することを好みますが、キャッシュなどのある種使い捨てのデータベースととらえる場合は何でもいいかなと思うので、公式推奨っぽい Id? 型を利用することにしました。

  • 複合インデックス

Composite indexes の項に複合インデックスについての解説があります。

https://isar.dev/indexes.html#composite-indexes

 Foo {
 
  String? bar;
  
  (
    unique: true,
    composite: [CompositeIndex('bar', type: IndexType.hash)],
  )
  String? baz;
}

みたいな定義をしておくと、

final foo = await isar.foo.getByBazuBar();

のように getByBazuBar() メソッドが(コード生成すると)生えてきます。便利。

付録

Awarefy でも利用したことのある、その他のローカルストレージ/データベースパッケージを掲載しておきます。

手軽な Key Value Store として利用できる SharedPreference

https://pub.dev/packages/shared_preferences

端末ローカルとはいえセンシティブな情報を管理したい場合に利用できる SecureStorage

https://pub.dev/packages/flutter_secure_storage

一定の複雑さの構造データを高速に扱いたい場合の(これまでの)定番パッケージ Hive

https://pub.dev/packages/hive

アウェアファイ 技術ブログ Zenn 支社

Discussion