FlutterでHiveのリレーションを試してみた

自作アプリはFirestoreをいきなり使って作成したわけですが、ローカルDB版も作りたいなと勉強を始めてみたが、詰まったのでそのあたりをメモ。

公式サイト：[https://docs.hivedb.dev/#/](https://docs.hivedb.dev/#/)

##リレーションの実現

リレーションを実現するのにHiveListを使うということだが、どうにも公式のドキュメントを見ても理解できず。で、自分なりにこういうことかと試してみてやっと思ったような動きになった。しかしこれで合ってるんやろか。

公式は１つのクラスでのサンプルとなっていますが、自分がやりたいのは別クラス。ってことでそれを説明。

##DBの型クラスの作成

下記のように２つ作ります。
キャンプのオブジェクトに対して複数のキャンプ道具を保持するという構成です。

```camp.dart 
import 'package:hive/hive.dart';
import 'gear.dart';

part 'camp.g.dart';

@HiveType(typeId: 0)
class Camp extends HiveObject {
  @HiveField(0)
  String name;
  @HiveField(1)
  late HiveList<Gear> gears;

  Camp(this.name);
}
```

partで設定するファイルはジェネレータで生成されるもの。
拡張子の前に「.g」を入れる。名前が違っていると生成されない。

```gear.dart 
import 'package:hive/hive.dart';

part 'gear.g.dart';

@HiveType(typeId: 1)
class Gear extends HiveObject {
  @HiveField(0)
  String item;

  Gear(this.item);
}
```

##ジェネレータでアダプター生成

ターミナルを起動して以下のコマンドを実行

```
flutter packages pub run build_runner build
```

camp.g.dart, gear.g.dart が生成されます。


##メイン処理

```main.dart 
void main() async {
  //Hiveの初期化
  await Hive.initFlutter();

  //アダプター
  Hive.registerAdapter(CampAdapter());
  Hive.registerAdapter(GearAdapter());

  //boxのオープン
  var camplistBox = await Hive.openBox<Camp>('camplist');
  var gearlistBox = await Hive.openBox<Gear>('gearlist');

  print('(1) add-------------------');
  //DBを初期化
  await camplistBox.clear();
  await gearlistBox.clear();

  //登録するデータの作成
  var camp1 = Camp('ソロキャン');
  var camp2 = Camp('グルキャン');

  //gearsにキャンプ道具リストをリレーション
  //(addする前にこれをしていないとエラーになる場合がある）
  camp1.gears = HiveList(gearlistBox);
  camp2.gears = HiveList(gearlistBox);

  //キャンプリストに追加
  camplistBox.addAll([camp1, camp2]);

  //登録するキャンプ道具の作成
  var gear1 = Gear('テント');
  var gear2 = Gear('タープ');
  var gear3 = Gear('椅子');

  //キャンプ道具リストへ追加
  //ここではキャンプ1，キャンプ2の両方に紐づけるのを全て入れておく
  gearlistBox.addAll([gear1,gear2,gear3]);

  //キャンプ1のgearsにgear1,gear2を登録
  camp1.gears.addAll([gear1,gear2]);

  //キャンプ2のgearsにgear3を登録
  camp2.gears.addAll([gear3]);

  int i=0;
  camplistBox.values.forEach((camp) {
    print(i.toString() + ':' + camp.name);
    camp.gears.forEach((element) {
      print('  >' + element.item);
    });
    i++;
  });
}
```

```
I/flutter ( 5913): (1) add-------------------
I/flutter ( 5913): 0:ソロキャン
I/flutter ( 5913):   >テント
I/flutter ( 5913):   >タープ
I/flutter ( 5913): 1:グルキャン
I/flutter ( 5913):   >椅子
```

これでデータが登録されてることを確認。

`gearlist.addAll([gear1,gear2,gear3]);`で先に子テーブルとなるキャンプ道具リストにデータを全て入れておくことを先にやり、その後で同じデータオブジェクトを使ってキャンプオブジェクトの中のgearsにそれに紐づけるものだけを追加していくという流れ。これが肝らしい。

##データの追加

```
  print('(2) add2--------------------');
  var camp_a = camplistBox.getAt(0);
  var gear_a = Gear('コット');
  gearlistBox.add(gear_a);
  camp_a?.gears.add(gear_a);

  i=0;
  camplistBox.values.forEach((camp) {
    print(i.toString() + ':' + camp.name);
    camp.gears.forEach((element) {
      print('  >' + element.item);
    });
    i++;
  });
```

```
I/flutter ( 5913): (2) add2--------------------
I/flutter ( 5913): 0:ソロキャン
I/flutter ( 5913):   >テント
I/flutter ( 5913):   >タープ
I/flutter ( 5913):   >コット
I/flutter ( 5913): 1:グルキャン
I/flutter ( 5913):   >椅子
```

##データの修正

```
  print('(3) update--------------------');
  var camp_b = camplistBox.getAt(1);
  var key = camp_b?.gears[0].key;
  var item = camp_b?.gears[0].item;
  print('gear0 item=' + item! + ',key=' + key.toString());
  //データの修正
  camp_b?.gears[0].item = 'ローチェア';
  camp_b?.gears[0].save();

  int i2=0;
  camplistBox.values.forEach((camp) {
    print(i2.toString() + ':' + camp.name);
    camp.gears.forEach((element) {
      print('  >' + element.item);
    });
    i2++;
  });
```

`camp_b?.gears[0].save();`で変更したデータが保存される。

```
I/flutter ( 5913): (3) update--------------------
I/flutter ( 5913): gear0 item=椅子,key=2
I/flutter ( 5913): 0:ソロキャン
I/flutter ( 5913):   >テント
I/flutter ( 5913):   >タープ
I/flutter ( 5913):   >コット
I/flutter ( 5913): 1:グルキャン
I/flutter ( 5913):   >ローチェア
```

##データの削除

```
  print('(4) delete--------------------');
  var camp_a2 = camplistBox.getAt(0);
  var key2 = camp_a2?.gears[1].key;
  var item2 = camp_a2?.gears[1].item;
  print('del item=' + item2! + ',key=' + key2.toString());
  //データの削除
  camp_a2?.gears[1].delete();

  int i3=0;
  camplistBox.values.forEach((camp) {
    print(i3.toString() + ':' + camp.name);
    camp.gears.forEach((element) {
      print('  >' + element.item);
    });
    i3++;
  });

  print('gearlist----------');
  gearlistBox.values.forEach((element) {
    print('  >' + element.item + ',key=' + element.key.toString());
  });
```

```
I/flutter ( 5913): (4) delete--------------------
I/flutter ( 5913): del item=タープ,key=1
I/flutter ( 5913): 0:ソロキャン
I/flutter ( 5913):   >テント
I/flutter ( 5913):   >コット
I/flutter ( 5913): 1:グルキャン
I/flutter ( 5913):   >ローチェア
I/flutter ( 5913): gearlist----------
I/flutter ( 5913):   >テント,key=0
I/flutter ( 5913):   >ローチェア,key=2
I/flutter ( 5913):   >コット,key=3
```