😎

Flutter Hive の基本的な使い方

2021/12/12に公開

Environment

  • MacBook Air (M1 2020)
  • Android Studio Arctic Fox | 2020.3.1 Patch 3

Reference

Install

$ flutter pub add hive
$ flutter pub add hive_flutter
$ flutter pub add hive_generator --dev
$ flutter pub add build_runner --dev

pubspec.yaml

pub addを実行すると、pubspec.yaml は以下のようになります。

name: hello_hive
description: A new Flutter project.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1

environment:
  sdk: ">=2.12.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.2
  hive: ^2.0.4
  hive_flutter: ^1.1.0

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^1.0.0
  hive_generator: ^1.1.1
  build_runner: ^2.1.5

flutter:
  uses-material-design: true

pub get を忘れずに


main.dart

以下のような簡単な画面を用意します。

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const HomeScreen(),
    );
  }
}

class HomeScreen extends StatelessWidget {
  const HomeScreen({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Hello, Hive!'),
      ),
      body: const Center(
        child: Text('Hello, Hive!'),
      )
    );
  }
}

main function #1 initialize

hive_flutterimportし、main関数を非同期化(Future, async)。runApp実行前には、Hiveの初期化メソッドを呼び出します。

import 'package:hive_flutter/hive_flutter.dart';

Future<void> main() async {
  await Hive.initFlutter();
  runApp(const MyApp());
}

main function #2 open box

任意で名付けた box を openBox で開きます。

late Box box;

Future<void> main() async {
  await Hive.initFlutter();
  box = await Hive.openBox('box');
  runApp(const MyApp());
}

about box?

Hiveに保存されているデータはすべてboxで整理されています。box は、SQLのテーブルに例えられますが、構造を持っておらず、何でも入れることができます。

小さなアプリであれば、box は1つで十分かもしれません。より高度な問題では、boxはデータを整理するのに最適な方法です。またboxは、機密データを保存するために暗号化することもできます。


main function #3 Type Adapters and Register Adapter

about Type Adapters

Hiveはすべてのプリミティブ型ListMapDateTimeUint8Listをサポートしています。他のオブジェクトを格納したい場合は、オブジェクトをバイナリ形式に変換するTypeAdapterを登録する必要があります。

私見ですが、後の拡張性を考慮するならば 基本的には TypeAdapter を 後述する generator で生成して TypeAdapter を registerAdapter() で登録するように統一しておくとコードの管理や可読性の面でベネフィットがあると思います。

TypeAdapter は

  • 自分で書く
  • generatorで生成する

の二択があります。

ほとんどの場合、生成された TypeAdapter は非常に優れた性能を発揮します。しかし、手動で書いたアダプタでも改善できる点がある場合もあります。


generate a TypeAdapter

lib ディレクトリに TypeAdapter ディレクトリを作成し、user.dart を用意します。これら命名は任意なので、自由な名前で定義してください。

lib
  ├── TypeAdapter
  │   └── user.dart
  └── main.dart

次に、型を作ります

import 'package:hive/hive.dart';

part 'user.g.dart';

(typeId: 1)
class User {
  (0)
  String name;

  (1)
  int age;

  User({required this.name, required this.age});
}

IDE上でエラーっぽい状態になっているかもしれませんが、一旦無視して大丈夫です。

次に以下のコマンドをプロジェクト直下に移動した後にターミナル上から実行してください。

$ flutter packages pub run build_runner build

[INFO] Generating build script...
[INFO] Generating build script completed, took 550ms

[INFO] Precompiling build script......
[INFO] Precompiling build script... completed, took 4.5s

[INFO] Initializing inputs
[INFO] Building new asset graph...
[INFO] Building new asset graph completed, took 469ms

[INFO] Checking for unexpected pre-existing outputs....
[INFO] Checking for unexpected pre-existing outputs. completed, took 1ms

[INFO] Running build...
[INFO] Generating SDK summary...
[INFO] 3.3s elapsed, 0/3 actions completed.
[INFO] Generating SDK summary completed, took 3.2s

[INFO] 4.4s elapsed, 2/3 actions completed.
[INFO] Running build completed, took 4.6s

[INFO] Caching finalized dependency graph...
[INFO] Caching finalized dependency graph completed, took 28ms

[INFO] Succeeded after 4.6s with 2 outputs (7 actions)

もしエラーが起きる様でしたら、以下のコマンドを実行してみてください。参考:StackOverflow

$ flutter pub get && flutter pub run build_runner build --delete-conflicting-outputs

成功すると、TypeAdapterディレクトリに user.g.dart ファイルが生成され、先ほど user.dart. で出ていたエラーも解消されます。生成された user.g.dart を以下に示します。

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'user.dart';

// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************

class UserAdapter extends TypeAdapter<User> {
  
  final int typeId = 1;

  
  User read(BinaryReader reader) {
    final numOfFields = reader.readByte();
    final fields = <int, dynamic>{
      for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
    };
    return User(
      name: fields[0] as String,
      age: fields[1] as int,
    );
  }

  
  void write(BinaryWriter writer, User obj) {
    writer
      ..writeByte(2)
      ..writeByte(0)
      ..write(obj.name)
      ..writeByte(1)
      ..write(obj.age);
  }

  
  int get hashCode => typeId.hashCode;

  
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is UserAdapter &&
          runtimeType == other.runtimeType &&
          typeId == other.typeId;
}

これで TypeAdapter をジェネレートする一連の流れは完了です。より詳しく知りたい方は公式を参照してください。


Register Adapter

HiveにTypeAdapterを使わせたい場合は、アダプターを登録する必要があります。そのためには2つのものが必要です。

  • Adapter の インスタンス
  • typeID

すべての型には一意の typeId があり、ディスクから値が返されたときに正しいアダプタを見つけるために使用されます。0 から 223 までのすべての typeId が使用可能です。

? 225個以上のTypeAdapterは登録できない? (そんなに使わないけど)

再び、main関数です。builde_runner で生成された user.g.dart は userAdapter クラスであり、それを registerAdapter に渡して登録します。

Future<void> main() async {
  await Hive.initFlutter();
  box = await Hive.openBox('box');
  
  Hive.registerAdapter(UserAdapter());
  
  runApp(const MyApp());
}

これで、TypeAdapter の登録は完了です。


Write (書き込み)

書き込みには put() を使います。box への書き込みは、Mapへの書き込みとほぼ同じです。よってKey-Valueで登録すると考えれば想像に易いです。

すべてのキーは、最大255文字のASCII文字列か、符号なしの32ビット整数でなければなりません。

Future<void> main() async {
  await Hive.initFlutter();
  box = await Hive.openBox('box');

  Hive.registerAdapter(UserAdapter());
  
  box.put('user', User(name: 'Adjaper', age: 14));
  
  runApp(const MyApp());
}

Read (読み込み)

読み込みには、get() を使います。ここでは box.get('user') で登録したデータを取得しています。以下のコードはboxから取り出したあとに、user.nameuser.age を Text に渡して表示しています。

class HomeScreen extends StatelessWidget {
  const HomeScreen({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
  
    User user = box.get('user');
    
    return Scaffold(
      appBar: AppBar(
        title: const Text('Hello, Hive!'),
      ),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Text(user.name),
            Text(user.age.toString()),
          ],
        )
      )
    );
  }
}

これで一連の流れは掴めたと思います。複数のUserを扱いたいといった場合には Map を使って登録するといった感じになります。お疲れ様でした。

ScreenShot

main.dart

import 'package:flutter/material.dart';
import 'package:hello_hive/TypeAdapter/user.dart';
import 'package:hive_flutter/hive_flutter.dart';

late Box box;

Future<void> main() async {
  await Hive.initFlutter();
  box = await Hive.openBox('box');
  Hive.registerAdapter(UserAdapter());
  box.put('user', User(name: 'Adjaper', age: 14));

  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const HomeScreen(),
    );
  }
}

class HomeScreen extends StatelessWidget {
  const HomeScreen({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    User user = box.get('user');
    return Scaffold(
      appBar: AppBar(
        title: const Text('Hello, Hive!'),
      ),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Text(user.name),
            Text(user.age.toString()),
          ],
        )
      )
    );
  }
}

Discussion