【Flutter】キャッシュの実装方法

# はじめに

Flutterで開発していく中で、キャッシュが必要な仕様に直面しました。
そこで、色々なキャッシュを実装しましたので、その方法を記します。

# キャッシュを保存する場所

キャッシュを保存する場所によって、実装が異なります。
なので、ここでは、キャッシュを保存する場所を列挙します。

### (1) メモリ

Flutterの変数に保存する方法です。

#### メリット

* Read/Writeが速い。
* データが扱いやすい。

#### デメリット

* アプリを終了した場合、破棄される。
* ネイティブプラットフォーム(Android/iOS)と共有しにくい。

### (2) ファイルシステム

ローカルにファイルとして保存する方法です。

#### メリット

* アプリを終了しても破棄されず、次回起動時も保持される。
* ネイティブプラットフォーム(Android/iOS)と共有しやすい。

#### デメリット

* Read/Writeが遅い。
* データが扱いにくい。

### (3) データベース

ローカルのデータベースに保存する方法です。

#### メリット

* アプリを終了しても破棄されず、次回起動時も保持される。
* データが扱いやすい。

#### デメリット

* Read/Writeが遅い。
* 使用するデータベースによって、ネイティブプラットフォーム(Android/iOS)と共有しにくいorできない。

# (1) メモリにキャッシュする実装方法

色々な実装方法があると思います。
その中で、自分が実装した方法を記します。

それは、**Singleton Pattern**を用いた方法です。

```dart:Singletonで実現したキャッシュ
class CacheClass {
  // Singleton Patternを実現
  static final CacheClass _instance = CacheClass._internal();
  CacheClass._internal();
  factory CacheClass() => _instance;

  // キャッシュで保持するデータ
  late Object data;
}
```

Singletonを用いる事で、**どこからでも**、同一のデータにアクセスできます。
どのdartファイルからでも。
どのインスタンスからでも。
どの関数からでも。

そのアクセス方法です。

```dart:main()から、キャッシュにアクセス
void main() {
  // キャッシュにWrite
  CacheClass().data = "Hello World!";
  // キャッシュをRead
  print(CacheClass().data);
}
```

```zsh:mainの実行結果
Hello World!
```

# (2) ファイルシステムにキャッシュする実装方法

実装方法は、おおよそ1つだと思います。
【追記(2021/12/20)】
他の実装方法を見つけました。
"shared_preferences"を用いる方法です。実装方法は割愛します。

```dart
import 'dart:async'; // 非同期処理用ライブラリ
import 'dart:io';    // ファイル出力用ライブラリ
import 'package:path_provider/path_provider.dart'; // ファイルを保存可能な場所を取得するライブラリ

// ファイルを保存するパスを取得する
Future<File> getFilePath(String fileName) async {
  final directory = await getApplicationDocumentsDirectory();
  return File(directory.path + '/' + fileName);
}

// テキストファイルの読み込み
Future<String> readTxtFile(String fileName) async {
  final file = await getFilePath(fileName);
  return file.readAsString();
}

// テキストファイルの書き込み
Future<void> writeTxtFile(String fileName, String data) async {
  final file = await getFilePath(fileName);
  file.writeAsString(data);
}

// バイナリファイルの読み込み
Future<List<int>> readBinaryFile(String fileName) async {
  final file = await getFilePath(fileName);
  return file.readAsBytes();
}

// バイナリファイルの書き込み
Future<void> writeBinaryFile(String fileName, List<int> data) async {
  final file = await getFilePath(fileName);
  file.writeAsBytes(data);
}
```

ネイティブプラットフォーム(Android/iOS)も、
ローカルファイルへアクセスする事は簡単だと思うので、キャッシュの共有が楽です。

# (3) データベースにキャッシュする実装方法

色々なデータベースを使う方法があると思います。
以下の記事に、Flutterで使えるデータベースについて纏められてます。

https://kabochapo.hateblo.jp/entry/2020/02/01/144411

その中で、自分が実装した方法を記します。
私は、**Hive**を使いました。

https://pub.dev/packages/hive

> Fast, Enjoyable & Secure NoSQL Database 
> 高速で楽しく安全なNoSQLデータベース

> Hive is a lightweight and blazing fast key-value database written in pure Dart. Inspired by Bitcask.
> Hiveは、純粋なDartで記述された軽量で非常に高速なKey-Valueデータベースです。 Bitcaskに触発されました。

実装は非常に簡単です。

```dart
void main() async {
  // DBを生成
  var box = await Hive.openBox('myBox');
  // DBへデータ追加
  box.put('name', 'David');
  // DBからデータ取得
  var name = box.get('name');
}
```

また、自前で作成したデータクラスを扱う事も可能です。

https://zenn.dev/tatsuhiko/books/b938417d5cb04d/viewer/6732cb#%E3%82%BF%E3%82%A4%E3%83%97%E3%82%A2%E3%83%80%E3%83%97%E3%82%BF%E3%83%BC%E3%81%AE%E4%BD%9C%E6%88%90

#### 補足

ネイティブプラットフォーム(Android/iOS)との共有は出来ません。
(ネイティブプラットフォームとの共有が必要な場合は、中々ないと思います。。)

また、高速性をアピールしてるだけあって、非常に高速です。


> 1000 read iterations
[<img width=300 src = "https://raw.githubusercontent.com/hivedb/hive/master/.github/benchmark_read.png">](https://pub.dev/packages/hive#benchmark)

> 1000 write iterations
[<img width=300 src = "https://raw.githubusercontent.com/hivedb/hive/master/.github/benchmark_write.png">](https://pub.dev/packages/hive#benchmark)

# さいごに

やってみた感想です。

色々なキャッシュがあるものの、その実装方法はどれも簡単でした:sunglasses:
特にHiveは気に入りました！　実装は簡単、処理は高速。良いことづくめですね:relieved:

キャッシュは、だいたいのアプリで必須だと思うので、参考になれば幸いです！
では:wave: