【Flutter】katana_modelの様にシステムを簡単に切り替えることが可能なファイルストレージパッケージ

こんにちは。広瀬マサルです。

後々リリース予定の総合フレームワークのために利用されるパッケージをいくつか紹介します。

今回はファイルストレージ周りのパッケージとなります。

Cloud Storage for Firebaseを後々利用するためのテスト用のパッケージとなります。

使い方をまとめたので興味ある方はぜひ使ってみてください！

# katana_storage

https://pub.dev/packages/katana_storage

# はじめに

画像ファイルを代表したファイルをアップロードする機能は様々なアプリで利用されます。

SNSやマッチングアプリなど画像ファイルをアップロードすることでコミュニケーションを円滑にすることが可能です。

ファイルをアップロードしてアプリで利用可能にするクラウドサービスは数多くありますが、Flutterを利用しかつFirestoreなどを利用している場合は[Cloud Storage for Firebase](https://firebase.google.com/products/storage)を利用することが最も適しているかと思います。

このパッケージでは[katana_model](https://pub.dev/packages/katana_model)で実装したときと同じ様にアダプターを利用してFirebaseやローカルでのファイルストレージを切り替えることが可能になるようなパッケージを実装しました。

# インストール

下記パッケージをインポートします。

```dart
flutter pub add katana_storage
```

Cloud Storage for Firebaseを利用する場合は下記のパッケージを合わせてインポートします。

```dart
flutter pub add katana_storage_firebase
```

# 実装

## 事前準備

必ず`StorageAdapterScope`のWidgetをアプリのルート近くに配置します。

adapterのパラメーターに`LocalStorageAdapter`といったStorageAdapterを渡します。

```dart
// main.dart
import 'package:flutter/material.dart';
import 'package:katana_storage/katana_storage.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return StorageAdapterScope(
      adapter: const LocalStorageAdapter(),
      child: MaterialApp(
        home: const StoragePage(),
        title: "Flutter Demo",
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
      ),
    );
  }
}
```

## ストレージオブジェクトの作成

まず、ファイル１つにつき１つの`Storage`オブジェクトを作成します。

作成時にリモート側の相対パスを記述した`StorageQuery`を渡します。

Storageオブジェクトは`ChangeNotifier`を継承しているので、`addListener`やriverpodの`ChangeNotifierProvider`などを合わせて利用し状態を監視することができます。

```dart
final storage = Storage(const StorageQuery("test/file"));
```

```dart
class StoragePage extends StatefulWidget {
  const StoragePage({super.key});

  @override
  State<StatefulWidget> createState() => StoragePageState();
}

class StoragePageState extends State<StoragePage> {
  final storage = Storage(const StorageQuery("test/file"));
  final controller = TextEditingController();

  @override
  void initState() {
    super.initState();
    storage.addListener(_handledOnUpdate);
  }

  void _handledOnUpdate() {
    setState(() {});
  }

  @override
  void dispose() {
    super.dispose();
    storage.removeListener(_handledOnUpdate);
    storage.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("App Demo")),
      body: TextField(
        controller: controller,
        expands: true,
        textAlignVertical: TextAlignVertical.top,
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          final text = controller.text;
          final bytes = Uint8List.fromList(text.codeUnits);
          await storage.uploadWithBytes(bytes);
        },
        child: const Icon(Icons.upload_file),
      ),
    );
  }
}
```

## アップロード

アップロードを行うには下記のメソッドが利用可能です。

- `upload`：ローカルにあるファイルパスを指定してアップロード。
- `uploadWithBytes`：バイトデータを渡して直接アップロード。

戻り値に`StorageValue`が渡されます。ここにアップロードしたファイルのリモートのフルパスが格納されています。

```dart
final bytes = Uint8List.fromList(text.codeUnits);
final storageValue = await storage.uploadWithBytes(bytes);
print("Remote path is: ${storageValue.remote.path}");
```

## ダウンロード

すでにストレージにアップロードされているファイルをダウンロードするには`download`のメソッドを利用します。

引数にローカルに保存するファイルの相対パスを指定することも可能です。

戻り値に`StorageValue`が渡され、ここにダウンロードしたファイルのローカルのフルパスやファイルの実データが格納されています。

ローカルファイルへの保存はWebでは対応していません。

```dart
final bytes = Uint8List.fromList(text.codeUnits);
final storageValue = await storage.download("file.jpg");
print("Local path is: ${storageValue.local.path}");
print("Local data is: ${storageValue.local.bytes}");
```

# StorageAdapter

`StorageAdapterScope`を定義する際に渡すことでストレージのシステムを変更することが可能です。

- `RuntimeStorageAdapter`：アプリのメモリ上のみにデータがアップロードされます。実際には保存されません。テスト時に利用してください。
- `LocalStorageAdapter`：端末のローカルにファイルを保存するためのストレージアダプター。downloadやuploadにおける`ローカル`と`リモート`はどちらも端末内にあることに注意してください。画面モックを実装する際に便利です。Webでは利用できません。
- `FirebaseStorageAdapter`：Cloud Storage for Firebaseを利用するストレージアダプター。アプリ間で利用可能です。Firebaseの[初期設定](https://firebase.google.com/docs/storage/flutter/start)が必要です。

# おわりに

自分で使う用途で作ったものですが実装の思想的に合ってそうならぜひぜひ使ってみてください！

また、[こちら](https://github.com/mathrunet/flutter_masamune)にソースを公開しているのでissueやPullRequestをお待ちしてます！

また仕事の依頼等ございましたら、私の[Twitter](https://twitter.com/mathru)や[Webサイト](https://mathru.net)で直接ご連絡をお願いいたします！

https://mathru.net/ja/contact

# GitHub Sponsors

スポンサーを随時募集してます。ご支援お待ちしております！

https://github.com/sponsors/mathrunet