[FlutterFlow] Resize Imagesを使って容量の小さい画像を自動作成



## はじめに
### FlutterFlowについて

FlutterFlowは、爆速モバイルアプリ開発を実現する、画期的なノーコードツールです。簡単なアプリなら本当にその日のうちにテストフライトまでが完了していると優れたツールです。
他のノーコードツールと違って書ける、Flutterアプリとして吐き出せる、という点が強みです。

https://flutterflow.io/

### 今回の目標

::: note info
この記事のゴールは、ユーザーからアップロードされる画像を処理して、低画質verの画像を自動生成することです。
:::

Firebaseにアップロードされる画像を自動処理して低画質版を作成するために、「[Resize Image](https://extensions.dev/extensions/firebase/storage-resize-images)」という拡張機能を使用します。

この拡張機能をざっくり説明すると、『Storageにアップロードされた画像に対して、自動的に低画質Verを作成してStorageに格納する』というものです。

Cloud Functionを使用して、アップロードされたデータをFirebaseサーバー内部で処理するため、セキュリティ的にも安全ですね。

これを導入したプロジェクトの実行例です。画像をアップロードすると、容量の小さい画像を自動で作成します。


![画像のアップロード例.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628974/647020f6-4110-0a48-e967-726c28d2cfca.png)


今回こちらの記事を参考にさせていただきました🙇

https://qiita.com/butterv/items/2194a776f55023ccd485


## 前提

以下の環境で進めます。

:::note info
- Firebaseのプロジェクトを作成済み（Blazeプラン）
- FlutterFlowバージョン等
    - FlutterFlow v4.1.40 released April 21, 2024
    - Flutter version is 3.19.1
- FlutterFlowでプロジェクトを作成済み & Firebaseのプロジェクトを連携している
:::

### 容量の小さい画像が必要になった背景
Xや、InstagramのようなSNSアプリを作成する際、必ずと言っていいほどアイコン画像が使われます。また、投稿に画像を添付する機能を有するアプリも多いと思います。
かくいう私も、それに類するアプリを複数作ってまいりました。

これらの画像について、ユーザーのアップロードしたものをそのまま使用すると、当然データ通信量が増加してしまいます。

Firebaseはデータ通信量に応じて課金額が増加するため、出来る限りデータ通信量を抑えることが重要です。
これを実現するために、アップロードされた画像はそのままに、追加で画質の低いデータを作成し採用したい、となったことがきっかけでした。

### 低画質Verを作る方法
そこで着目したのがFirebaseのExtension「[Resize Images](https://github.com/firebase/extensions/tree/master/storage-resize-images)」です。
ざっくり説明すると、Storageにアップロードされた画像に対して、あらかじめ設定したサイズやフォーマット等に自動変換してくれる追加機能です。

## Resize Imagesの導入


### Installについて
まずはFirebaseのプロジェクト（なんでもいい）を開いてExtensionsを選択します。

![Extensions.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628974/d416ff57-efb5-6d85-4dd4-f5fa34332ecb.png)


次に拡張機能を探します。検索窓に「Resize Images」と入力してください。対象のExtensionが表示されるので、Installを押下します。

![search.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628974/e7f878d9-af81-061d-ece5-9d71c756a960.png)


どのProjectにInstallするかを選択する画面に遷移するので対象のProjectを選択してください。

### 詳細設定
次にResize時の詳細設定です。どのように使用するかに応じて、各パラメータの設定値は変わってくるので、詳細は公式のDocsに任せることとします。

参考までに、私が使用した設定を掲載します。この場合

- リサイズされて作成されるファイルは2種類の大きさがあり、それぞれ「50x50」「200x200」となります
- アップロード元のファイルは削除されません
- 作成されたリサイズ後の画像は自動で公開されます
- 作成されたリサイズ後の画像は、`user/{userId}/uploads/resized_images/` 以下に格納されます

![設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628974/263fa65a-e11d-d163-5cc8-ad40cef6e916.png)

そのため、アップロードされたファイルは次のような構造になります。

```plaintext:tree
users
└ {userId}
　 └ uploads
　 　 ├ image.png
　 　 └ resized_images
　 　 　 ├ image_50x50.png
　 　 　 └ image_200x200.png
```

## FlutterFlowから画像をアップロードする処理の作成

### アップロードの実装
アップロードは通常通り「Upload Media」で実装します。
![画像アップロード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628974/fc5fa090-5f61-a745-b94b-2cf28fa10e9a.png)

この際、Widget Stateには、この画像を表示するためのURLが入力されています。
ただし、今回欲しいのは、その画像を元に作成したリサイズ済みの画像URLです。

そのため、Widget Stateを用いてリサイズ済みの画像URLを取得する方法を実装します。

### オリジナル画像とリサイズ画像のURL
オリジナル画像とリサイズ画像のURLは次のようになっています。

```plaintext:Compare
https://firebasestorage.googleapis.com/v0/b/project.appspot.com/o/users%2F{userId}%2Fuploads%2F1713633570803000.png?alt=media&token=hoge

https://firebasestorage.googleapis.com/v0/b/project.appspot.com/o/users%2F{userId}%2Fuploads%2Fresized_images%2F1713633570803000_50x50.png?alt=media&token=fuga
```

違うところは、次の2つが追加されていることです。

- resize_imagesというパス
- ファイル名の後にある_50x50とサイズを表す文字列

### リサイズ画像を表示する用のURLを作成

したがって、元の画像URLに対してこれらの文字列を追加した新たなImagePathを返すCustom Functionを作成します。

::: note warn
Resize Imageの設定で、保存先のディレクトリ名を変更している場合は、以下のコードの41行目、「resized_images」の部分を書き換えてください。
:::

```dart
...

String returnResizedImagePath(String originalImagePath) {
  /// MODIFY CODE ONLY BELOW THIS LINE

  var uri = Uri.parse(originalImagePath);
  var segments = List<String>.from(uri.pathSegments);

  var fileSegment = segments.last;

  // ファイル名から拡張子を分離
  var dotIndex = fileSegment.lastIndexOf('.');
  var filename = fileSegment.substring(0, dotIndex);
  var extension = fileSegment.substring(dotIndex);

  // リサイズされた画像のファイル名を生成
  var resizedFilename = '${filename}_50x50${extension}';
  int uploadsIndex = resizedFilename.indexOf('uploads/') + 'uploads/'.length;
  var newFilename = resizedFilename.substring(0, uploadsIndex) + 'resized_images/' + resizedFilename.substring(uploadsIndex);
  segments[segments.length - 1] = newFilename;

  // 新しいURLを生成
  var newUri = uri.replace(pathSegments: segments);
  Map<String, dynamic> newQueryParams = Map.from(newUri.queryParameters);
  newQueryParams.remove('token');
  var resizedImagePath = newUri.replace(queryParameters: newQueryParams).toString();

  return resizedImagePath;

  /// MODIFY CODE ONLY ABOVE THIS LINE
}
```



あとは、必要なところでこれを呼び出せば完了です。

## 最後に
FlutterFlow、最近もどんどんアップデートされてきていますね。
これからも使い続けたいです。