すらぼうの開発ノート

モバイルアプリエンジニアのメモ

【Flutter】hiveの使用方法

本エントリではhiveを使ったデータ管理方法を説明する。



hiveについて

特徴

hiveは以下の特徴をもつデータベース。

  • 動作が軽量
  • キーバリュー型
  • boxという単位でデータを管理

パフォーマンス

hiveの公式リポジトリにはsqfliteshared_preferencesとのパフォーマンスの比較が掲載されている。

1000回連続でデータ読み取り、生成を行なった結果が上画像。 いずれもhiveはその他のパッケージと比べて処理時間が短く この操作ではパフォーマンスが良いことが示されている。

公式リンク

pub.dev

docs.hivedb.dev


基本的な使用方法

実装の流れ

hiveを使用する基本的な流れは以下。非常にシンプル。

  • pubspec.yamlを更新してインストール
  • Hive.initFlutter()hiveにデータの保存先を認識させる
  • Hive.openBox( boxの名前 )でboxを開く
  • ValueListenableBuilderでboxを使用したいウィジェットをラップ
  • put()get()などのメソッドでデータを処理

以降上から順に説明する。

また説明用として公式ドキュメントのFavorite Booksアプリを用いる。 Favorite Booksのリポジトリこちら。各説明では必要な箇所のみを抜粋した。

pubspec.yamlを更新してインストール

hiveサードパーティのパッケージなので使用する際にはpubspec.yamlに以下を記述する。

dependencies:
  hive: ^2.2.3
  hive_flutter: ^1.1.0

Hive.initFlutter()でhiveにデータの保存先を認識させる

void main() async {
  await Hive.initFlutter();
  runApp(MyApp());
}

main()関数内でアプリ起動直後に実行していることが多い。 この処理でhiveにデータの保存先を認識させる。

Hive.openBox( boxの名前 )でboxを開く

const favoritesBox = 'favorite_books';

void main() async {
  await Hive.initFlutter();
  await Hive.openBox<String>(favoritesBox);
  runApp(MyApp());
}

この処理もmain()関数内でアプリ起動直後に実行していることが多い。 openBox()の引数にはboxの名称が入る。boxの名称は何度も使用するので定数として定義すると楽。

ValueListenableBuilder でboxを使用したいウィジェットをラップ

class MyApp extends StatefulWidget {
  ..
}

class _MyAppState extends State<MyApp> {
  late Box<String> favoriteBooksBox;

  @override
  void initState() {
    super.initState();
    favoriteBooksBox = Hive.box(favoritesBox);
  }
  
  ...

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
    ...,
      home: Scaffold(
      ...,
        body: ValueListenableBuilder(
          valueListenable: favoriteBooksBox.listenable(),
          builder: (context, Box<String> box, _) {
            return ListView.builder(
              itemCount: books.length,
              itemBuilder: (context, listIndex) {
                return ListTile(
                ...
                );
              },
            );
          },
        ),
      ),
    );
  }
}

ValueListenableBuilderでboxが必要なウィジェットをラップする。

valueListenableにはValueListenable<Box<T>>型のインスタンスを渡す必要があるので、listenable()メソッドで作成する。 実際にboxからデータを使用するにはbuilderプロパティ内で匿名関数の第二引数boxを利用する。

put()やget()などのメソッドでデータを処理

class _MyAppState extends State<MyApp> {


  Widget getIcon(int index) {
    if (favoriteBooksBox.containsKey(index)) {
      return Icon(Icons.favorite, color: Colors.red);
    }
    return Icon(Icons.favorite_border);
  }

  void onFavoritePress(int index) {
    if (favoriteBooksBox.containsKey(index)) {
      favoriteBooksBox.delete(index);
      return;
    }
    favoriteBooksBox.put(index, books[index]);
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
    ...
      home: Scaffold(
      ...
        body: ValueListenableBuilder(
          valueListenable: favoriteBooksBox.listenable(),
          builder: (context, Box<String> box, _) {
            return ListView.builder(
              itemCount: books.length,
              itemBuilder: (context, listIndex) {
                return ListTile(
                  title: Text(books[listIndex]),
                  trailing: IconButton(
                    icon: getIcon(listIndex),
                    onPressed: () => onFavoritePress(listIndex),
                  ),
                );
              },
            );
          },
        ),
      ),
    );
  }
}

boxで管理しているデータを利用するにはput()get()delete()プロパティを使用する。 それぞれの記述方法は以下。

put()

boxに値を挿入する。もし既にキーが存在する場合は値が更新される。 文法は以下の通り。

boxインスタンス.put(キー名称, 値);

favorite booksアプリでは以下の箇所で使用している。

void onFavoritePress(int index) {
  if (favoriteBooksBox.containsKey(index)) {
    favoriteBooksBox.delete(index);
    return;
  }
  favoriteBooksBox.put(index, books[index]);
}

公式ドキュメントの説明は以下。

docs.hivedb.dev

get()

boxからキー名称で指定した値を取得する。キーが存在しない場合はnullが返される。 文法は以下の通り。

boxインスタンス.get(キー名称);

favorite booksアプリでは使用していない。 公式ドキュメントの説明は以下。

docs.hivedb.dev

delete()

box内のキー名称で指定した値を削除する。

boxインスタンス.delete(キー名称);

favorite booksアプリでは以下の箇所で使用している。

void onFavoritePress(int index) {
  if (favoriteBooksBox.containsKey(index)) {
    favoriteBooksBox.delete(index);
    return;
  }
  favoriteBooksBox.put(index, books[index]);
}

公式ドキュメントの説明は以下。

docs.hivedb.dev


ユーザー定義クラスを用いる際の使用方法

hiveを用いてユーザー定義クラスを扱う場合、TypeAdapterを用いてクラスをhiveが認識できるようにする必要がある。

このTypeAdapterの実装はフルスクラッチで書くこともできるが、一般的にはbuild_runner を用いた実装の方が楽。

本エントリではbuild_runnerを用いた実装を説明する。手順は以下。

  • pubspec.yaml更新
  • ユーザー定義クラスに追記
  • build_runnerの実行
  • ユーザー定義クラスをhiveへ登録

pubspec.yaml更新

hive_generatorbuild_runnerをインストールする。 インストール時には最新バージョンを確認。

dev_dependencies:
  ...
  hive_generator: ^1.1.1
  build_runner: ^2.1.5

ユーザー定義クラスに追記

以下のユーザー定義クラスを例に説明する。

class Person {
  String name;
  int age;
  List<Person> friends;
}

Personクラスをhiveに認識させるため以下のように記述する。

import 'package:hive/hive.dart';

part 'person.g.dart';

@HiveType(typeId: 1)
class Person {
  @HiveField(0)
  String name;

  @HiveField(1)
  int age;

  @HiveField(2)
  List<Person> friends;
}

このコードのポイントを説明を行う。

partキーワード

part 'person.g.dart';

partはファイルを分割する際に用いられるキーワードで、person.g.dartperson.dartの一部だという意味。 person.g.dartbuild_runnerを実行した際に自動で作成されるファイル。 一般的にbuild_runnerで自動作成(generate)されたファイル名には```.g````が付く。

@HiveType(typeId: )

@HiveType(typeId: 1)

はこのユーザー定義クラスをhiveが認識するためのキーワード。 クラス名の先頭につける。

このように@で始まるキーワードはannotationと呼ばれる。

typeIdプロパティに渡す数字はこちら,-Annotate%20all%20fields)に記載されている通り、0~223の数値を渡す。この数値は他のクラスと重複してはダメなので、hiveに登録できるユーザー定義クラスは224個という制限がある。

@HiveField()

@HiveField(0)はユーザー定義内のプロパティにそれぞれ付けるannotaiton。 括弧内に渡す数値はプロパティごとにクラス内でユニークであれば良いとのこと。

build_runnerの実行

ターミナルなどのCLIで以下のコマンドを実行する。

flutter packages pub run build_runner build

これでperson.g.dartperson.dartと同じディレクトリに生成される。

ユーザー定義クラスをhiveへ登録

HiveクラスのメソッドregisterAdapter()を用いてPersonクラスをhiveに認識させる。 次のようにmain()の中で実行されることが多い。

Future<void> main() async {
  await Hive.initFlutter();
  Hive.registerAdapter(PersonAdapter());
  ...
}

以上でユーザー定義クラスをhiveで扱うことができる。


hive以外のデータ管理パッケージ

Flutterにはhive以外にもデータを永続化して管理する手段がある。 以下のパッケージがhive以外の候補として上がることが多い。

  • sqflite
  • shared_reference

これらの使用方法などについては以下のエントリでまとめている。

note-tmk.hatenablog.com

note-tmk.hatenablog.com