FlutterでHiveというデータベースを使用してみたが、色々ハマったのでちゃんと動くところまでのメモ。SQLiteのようにテーブル的に保存していくような形。
元々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はクラス内でユニーク。
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);
}
}
まずモデル定義したファイルのimportの下あたりに下記のような行を追加する。
part 'custom_panel.g.dart';
ファイルが無いため最初はエラーになるが、生成を実行するとここにファイルが作られる。実行コマンドは下記。
flutter packages pub run build_runner build
ちなみにリレーションを使っている場合はまずリレーションのないモデルのアダプターを一通り生成しておいてからリレーションのあるモデルを生成する必要がある。リレーション込でいきなりやってしまうとエラーになる。
あと、試しにアダプタを別フォルダに生成してもらうと思ったがダメだった(今のところは)。
細かくは下記の本家ドキュメント。
以上、使い始めたばかりの情報なので今後も何か出るかも。
Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。
また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!
こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?
コメント