[Flutter]Isarでローカルストレージにデータを保存する

この記事は、【 可茂IT塾 Advent Calendar 2024 】の13日目の記事です。

# はじめに
皆さんは、Flutterにてローカルストレージにデータを保存したいときはどのようにしているでしょうか？
その手軽さから、自分含めおそらく[shared_preferences](https://pub.dev/packages/shared_preferences)というパッケージを用いている人が多いのではないかと思います。
ですが今回、あえてshared_preferencesではないパッケージを用いてローカルストレージにデータを保存する方法を採用したので、shared_preferencesと比較した違いやメリット・デメリットについて忘備録も兼ねてまとめておきたいと思います。
Flutterでローカル保存の方法を検討している方の助けになれば幸いです。

# 今回紹介するパッケージ
今回紹介するのは、isarというパッケージです。
ちなみに読み方は**イザー**らしいです。読めない（笑）

https://pub.dev/packages/isar

iOS,AndroidだけでなくMacやWindows,Linuxにも使用できます。

# 導入方法（注意点付き）
このパッケージ、すごく丁寧でなんと個別のサイト（しかも多言語対応）が用意されているんです。
pub.devだけでなく個別のドキュメントがあるパッケージというのは珍しいのではないかと思います。

https://isar.dev/ja/tutorials/quickstart.html

正直導入に関してはこのクイックスタートを読めばできると思うので割愛しますが、一点だけ特筆しておきます。
クイックスタート通りにパッケージを追加するだけでは自分はうまくいかず、pubspec.yamlを以下のように手動で編集する必要がありました。

```dart:pubpec.yaml
dependencies:
  isar: ^3.1.0
  isar_flutter_libs: ^3.1.0
  path_provider: ^2.1.5  //これもドキュメントにはないですが使うので追加しましょう

dev_dependencies:
  build_runner: ^2.4.11
  isar_generator: ^3.1.0
```

ネットで調べた感じ、結構同様の訴えをしていた人を見かけたので、もしうまく行かなかった方はこれを試してみるのも良いと思います。


# 使用方法

### データの作成

まずは、保存したいデータを作成します。今回はisarの一つの利点である**自作のクラス**としてPersonクラスを作成・保存してみたいと思います。

```dart
part 'person.g.dart';

// 性別の列挙型。配慮が足りてなかったらごめん
enum Gender { male, female, other }

@collection
class Person {
  Id id = Isar.autoIncrement; // isar側のidを自動でインクリメントしてくれる機能。基本的にこう書く

  String name; // 身長
  int age;  // 年齢
  double height;   // 体重

  @Enumerated(EnumType.name)
  final Gender gender;
}
```

こんな感じで、classの上には*collection*アノーテーションをつけます。
また、列挙型を用いる場合は*Enumerated*をつけた上で、EnumTypeを選択する必要があります。選択は以下の中から選びましょう。
| タイプの種類 | 詳細 |
|:-:|:-:|
|original| 列挙型のインデックスは byte として格納されます。これは非常に効率的ですが、null 値を許容する enum は使用できません。  |
|original32| 列挙型のインデックスは short (4 バイトの整数) として格納されます。  |
|  name | 列挙名称は String として格納されます。  |
|  value |  列挙値の取得には、カスタムプロパティを使用します。 |

### build_runnerの実行
コマンドラインにて、以下のコマンドを叩きます。
```
flutter pub run build_runner build
```
これでDBの作成は完了しました！なんてお手軽！

::: note info
ここで、自分は、正しく記述してbuild_runnerをrunさせても何故かうまくいきませんでした。
試行錯誤の末、結局VScodeを再起動したら治ったので、もし何度チェックしても上手くいかないよ〜という方は**VScodeの再起動**も試してみても良いかもしれません。
:::

### データベースのインスタンスを作成
次に、DBを操作したい箇所にて次のようにコードを記述します。
```dart
final dir = await getApplicationDocumentsDirectory(); //ここでpath_providerパッケージが必要
final isar = await Isar.open(
  [PersonSchema],
  directory: dir.path,
);
```
ここで、**PersonSchema**というのは先ほどPersonクラスを作成したことで自動生成されたスキーマです。利用したい全てのデータ形に対してのスキーマを追加する必要があります。
例えば、もしAppleクラスとBananaクラスを利用したい場合は
```dart
final isar = await Isar.open(
  [AppleSchema,BananaSchema],
  directory: dir.path,
);
```
と書く必要があります。

:::note info
### openについて
Isar.open()に関しては、一度行えば良いものであり、DBを使用するたびにopenやcloseをする必要はないと記載されています。
そのため、openはmain.dart内で一度だけ行い、インスタンスを使用箇所に渡していくのが良いと思います。
:::

### データの取得、更新
ここまで来ればあとは好きにデータを保存・取得・更新するだけです！
先ほど取得したisarインスタンスを使用して更新していきます。
```dart
// データの挿入と更新
await isar.writeTxn(() async {
  await isar.users.put(newUser); 
});

int id = 1; // 任意のid

// データの取得
final existingUser = await isar.users.get(id);

// データの削除
await isar.writeTxn(() async {
  await isar.users.delete(id);
});
```
基本的にはidを用いてデータベースを操作していきます。他のDBと同じようにwhereやfilterを用いて検索することも可能です。
詳しくは下記を参照すると良いと思います。

https://isar.dev/ja/crud.html

# 今回Isarを選択した理由

自分が今回、他のパッケージでなくisarを使った理由を列挙しておきます。
### 自作クラスを使いたかった 
自分は今回、自作クラスをローカルに保存しようとしていました。shared_preferencesで自作クラスを保存する場合、**一度Jsonに変換してから保存する**という方法があるらしいのですが、ちょっと面倒に感じたのとそんなことせずにもっといいパッケージあるんじゃね？と思って探し始めたのが今回の一番の動機です。

↓参考

https://qiita.com/Hiiisan/items/4ba3739e286a8025ac1c

### enum（列挙型）とDateTime型を使いたかった
上と似ていますが、自作クラス内でenumとDateTimeを使っていました。これらをshared_prerencesで使うためには、やはり一度Stringに変換する必要があったので、これをちょっと面倒に感じたのとそんなことせずに（以下略）

### shared_preferences以外のパッケージを使ってみたかった
これはちょっと後付け気味なのですが、ローカル保存のパッケージが色々あるというのは前々から知っていたものの、shared_preferencesで困ったことがなかったので手をつける機会がありませんでした。そんな中今回の機会があったので、これはいいチャンスだと思い触れてみることにしました。

# 他のパッケージ（shared_preferences,Hive等）との比較
ローカル保存に用いられる代表的なパッケージ二つとの比較について述べます。
### shared_preferences vs isar
まず、導入の手軽さと使いやすさいう観点ではshared_preferencesに分があると思います。
isarが導入しづらいとか使いやすいということは決してないのですが、shared_preferencesはあまりにも手軽です。また、非常に多くの人が利用しているため詰まった時の情報の豊富さでも分があると思います。

一方で、重要なデータの管理という点ではisarの方が優れていると思います。shared_preferencesのドキュメントに以下のような記述があります。

>データは非同期的にディスクに永続化される可能性があり、戻り後に書き込みがディスクに永続化される保証はないため、このプラグインは重要なデータの保存には使用しないでください。

すなわち、shared_preferencesの方は重要なデータの管理には非推奨というようです。

また、扱うことができる型の種類の豊富さでもIsarが優れています。
shared_preferencesでは
- int型
- double型
- bool型
- String型
- List<String>型

ですが、Isarでは先述した通り上記に加えてDateTimeやfloat,enumなどに対応しており、さらに自作クラスも利用することができます。（個人的にはこれが一番のメリットでした）

↓参考

https://pub.dev/packages/shared_preferences


### Hive vs Isar
これは、isar公式ドキュメントに次のように書かれています。

> Isar vs Hive
>答えは簡単です: Isar は Hiveの代替としてスタート し、現在はHiveよりもIsarを使うことを推奨する状態になっています。

これを信じるなら、今Hiveを使う理由は特になさそうです。

# Isarを使って良かったこと
### Isar Inspectorが便利すぎる
Isarで保存したデータをブラウザ上で閲覧することができる機能[Isar Inspector]が、思っていた10倍くらい便利で驚きました。
DBを扱ったことがある方はわかると思うんですが、DB周りって結構デバッグが面倒なんですよね。一つデータ追加できたかを確認するだけでも一苦労で…
そんな中この**Isar Inspector**は、とても直感的だし見るだけじゃなく簡単なDBの操作までできてしまいます。
![スクリーンショット 2024-12-11 18.24.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/702415/812decf2-c416-d6d4-cf7c-d75a52865922.png)
↑このURLを開くと…
![mosaic_20241211183504.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/702415/a9ad6607-9f00-763e-a133-a27e3c3a1df6.png)
こんな感じの画面が開けます！（開発中のアプリのためモザイクだらけですみません笑）

この機能、とにかく便利なのでこの機能だけでもぜひ体験してみて欲しいです！

# まとめ
いかがでしたでしょうか。
使ってみた感想としては、非常に使いやすく高機能だったので、個人的には次もIsarを使用したいと考えるほど便利だと感じています。
今までshared_preferencesしか使ったことがないけど、Isarを使ってみたいと思っている方にとってこの記事が助けとなれば嬉しいです！


# 参考文献
https://kabochapo.hateblo.jp/entry/2020/02/01/144411

https://github.com/isar/isar/discussions/1377