2021-01-30に更新

FlutterでHiveというデータベースを使ってみての要所まとめ

FlutterでHiveというデータベースを使用してみたが、色々ハマったのでちゃんと動くところまでのメモ。SQLiteのようにテーブル的に保存していくような形。

https://docs.hivedb.dev/#/

Hiveはどうか

元々sqfliteというSQLiteのパッケージを使っていたが、やはりマイグレーションを書くのもモデルをMapに変換したりするのもひたすら面倒くさすぎるのでもっと簡単なものを探していた。下記でたくさんまとめられている。

Dart/FlutterのローカルDBの比較 - のんびり精進

その中でHiveというパッケージがスター数も多かったため(という理由だけで)使ってみることにした。

変換はなくなったし、リレーションもできたりするので今のところ満足。しかしハマりどころも多かったので使い方を含めメモしておく。

インストール

まずFlutterで使うためには下記のパッケージが必要となる。

上の2つは必須。hive_generatorはどちらでも良いが、クラスで作ったデータをテーブルやコレクション的に保存して使いたい場合、TypeAdapterというのを作る必要がある。それを生成してくれるために使う。ただし、自分で書くこともできるため絶対に必須というわけではない。build_runnerはhive_generatorでTypeAdapterを生成するコマンドを実行するために使う。hive_generatorだけインストールして生成コマンドを実行すると下記のようなメッセージが出る。

Could not find package "build_runner". Did you forget to add a dependency?

インストールできない場合

僕の場合、hiveとhive_flutterはインストールできたが、hive_generatorとbuild_runnerがダメだった。既存の色んなパッケージとバージョンが合わなかったため。(追記:2021/1 もしかしたらintlかも。build_runnerを下げたらいけたかも?)

他のパッケージも古くなっていそうだったため最新のものにバージョンアップしていけばよかったのかもしれないが、色々いじりすぎて動かなくなったりエラーがでるようになったりすると怖かったため、hive_generatorとbuild_runnerはインストールをしないことにした。TypeAdapterはさほど量も多くないし自分で書こうと思っていたため。

ちなみに後述もするが、同様の問題が出ている方は別のまっさらなプロジェクトを作ってそちらにhive関連をインストールして作ったモデルファイルをコピーすれば生成できる。

型クラスの作り方

下記のような感じ。CustomPanel has many CustomPanelCodeという構成の例。

import 'package:anyone_composer/models/custom_panel_code.dart';
import 'package:hive/hive.dart';

@HiveType(typeId: 0)
class CustomPanel extends HiveObject {
    @HiveField(0)
    String name;

    @HiveField(1)
    HiveList<CustomPanelCode> codes;

    CustomPanel(this.name) {
      final customPanelCodes = Hive.box<CustomPanelCode>('custom_panel_codes');
      codes = HiveList(customPanelCodes);
    }
}

起動時の初期化

hive_flutter側は自動でインポートされないので追記。

import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';
void main() async {
  await Hive.initFlutter();

  Hive.registerAdapter(CustomPanelCodeAdapter());
  Hive.registerAdapter(CustomPanelAdapter());
  await Hive.openBox<CustomPanelCode>('custom_panel_codes');
  await Hive.openBox<CustomPanel>('custom_panels');
  runApp(MyApp());
}

最初だけ、openBoxでロードする。そのあとはアプリケーション内ではawaitなしのboxメソッドで参照を取得することができる。

アノテーション

HiveTypeとHiveFieldは必要。HiveTypeは全体でユニーク、HiveFieldはクラス内でユニーク。

Auto Incrementされた値

HiveObjectの継承は、auto_incrementの値を利用する時に必要。こうすることで登録された後は key というプロパティで primary key を参照することが可能になる。

リレーション

リレーションは HiveList(ボックスの参照) という形で利用できる。

リレーションの利用

リレーションは初期化されていれば普通にaddとかで登録できる。ただRDBなどと違うのが、あくまでも実データのリレーションであり、存在しない値を使うことができない。そのため、親データを登録する前にまず実データを登録してからリレーションデータを登録する必要がある。

    await customPanelCodes.addAll(newCustomPanelCodes);
    customPanel.codes.addAll(newCustomPanelCodes);
    await customPanels.add(customPanel);

一番上の行の事前登録処理がないと下記のようなエラーが出る。

Unhandled Exception: HiveError: HiveObjects needs to be in the box "custom_panel_codes".

リレーション周りが動かない時

最初に起動して操作する限りは正常に動いてはいるっぽいのだが、再起動時のデータの読み込みに失敗しているときがあった。どうもリレーション時を利用している時に、データの読み込みに失敗しているらしい。TypeAdapterの reader.read() した値をオブジェクトに代入する時に型エラーが出る。

_TypeError (type 'HiveListImpl<HiveObject>' is not a subtype of type 'HiveList<CustomPanelCode>')

これが難題で、色々キャストしてみてもうまくいかなかった。

ということで、別のまっさらなプロジェクトにhive_generatorを含めたHive関連のパッケージをインストールし、モデルのクラスファイルをコピーしてジェネレートしてみたところ、下記の記述を見つけることができた。

    return CustomPanel(
      fields[0] as String,
    )..codes = (fields[1] as HiveList)?.castHiveList();

そう、castHiveListというメソッドを使う……! こんなのほぼ解説されているページもなく、実際のコード例もないのでハマりどころ。支障がなければhive_generatorをちゃんとつかうのが良さそう。最終的に下記のようなアダプタにした。

import 'package:anyone_composer/models/custom_panel.dart';
import 'package:hive/hive.dart';

class CustomPanelAdapter extends TypeAdapter<CustomPanel> {
  @override
  final typeId = 0;

  @override
  CustomPanel read(BinaryReader reader) {
    return CustomPanel(reader.read())
      ..codes = (reader.read() as HiveList)?.castHiveList();
  }

  @override
  void write(BinaryWriter writer, CustomPanel obj) {
    writer.write(obj.name);
    writer.write(obj.codes);
  }
}

hive_generatorの使い方

まずモデル定義したファイルのimportの下あたりに下記のような行を追加する。

part 'custom_panel.g.dart';

ファイルが無いため最初はエラーになるが、生成を実行するとここにファイルが作られる。実行コマンドは下記。

flutter packages pub run build_runner build

ちなみにリレーションを使っている場合はまずリレーションのないモデルのアダプターを一通り生成しておいてからリレーションのあるモデルを生成する必要がある。リレーション込でいきなりやってしまうとエラーになる。

あと、試しにアダプタを別フォルダに生成してもらうと思ったがダメだった(今のところは)。

細かくは下記の本家ドキュメント。

Generate adapter

まとめ

以上、使い始めたばかりの情報なので今後も何か出るかも。

ツイッターでシェア
みんなに共有、忘れないようにメモ

だら@Crieit開発者

Crieitの開発者です。 Webエンジニアです(在宅)。大体10年ちょい。 記事でわかりにくいところがあればDMで質問していただくか、案件発注してください。 業務依頼、同業種の方からのコンタクトなどお気軽にご連絡ください。 業務経験有:PHP, MySQL, Laravel, React, Flutter, Vue.js, Node, RoR 趣味:Elixir, Phoenix, Nuxt, Express, GCP, AWS等色々 PHPフレームワークちいたんの作者

Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

有料記事を販売できるようになりました!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?

コメント