AWS の開始方法
Flutter アプリケーションを構築する
AWS Amplify を使用してシンプルな Flutter アプリケーションを作成する
モジュール 4: 画像保存機能を追加する
このモジュールでは、ストレージと、画像をアプリのメモに関連付ける機能を追加します。
はじめに
認証されたユーザーだけがこのアプリに入ることができるので、写真を撮影してアプリの Amazon S3 バケット内の非公開フォルダにアップロードすることをユーザーに許可できます。
このモジュールでは、お使いの Amplify アプリにストレージカテゴリを追加し、デバイスのカメラで撮影した写真をアップロードして、個々のユーザーに関連付けられたすべての写真をダウンロードおよび表示します。
学習内容
- ストレージカテゴリの設定
- Amazon S3 へのファイルのアップロード
- Amazon S3 からのファイルのダウンロード
- URL からの画像の表示とキャッシュ
主要な概念
ストレージ - ストレージの概念は、ファイルを特定の場所に保存して、それらのファイルを必要な時に取り出せることです。このケースでは、Amazon S3 に画像を保存し、そこから画像をダウンロードすることです。
所要時間
20 分
使用するサービス
実装
-
ストレージサービスを作成する
プロジェクトのルートディレクトリで次のコマンドをターミナルに入力して、ストレージサービスを Amplify プロジェクトに追加します。
amplify add storage
他のカテゴリと同様に、Amplify CLI はストレージサービスの設定方法に関する質問を表示します。Enter キーを押して、ほとんどの質問にデフォルトの回答で答えます。
➜ photo_gallery git:(master) amplify add storage ? Please select from one of the below mentioned services: Content (Images, audio , video, etc.) ? Please provide a friendly name for your resource that will be used to label th is category in the project: s33daafe54 ? Please provide bucket name: photogalleryf3fb7bda3f5d47939322aa3899275aab ? Who should have access: Auth users only
認証済みユーザーに必要なアクセス権限の種類を指定する場合は、キーを押して「create/update」、「read」、「delete」を選択します。
? What kind of access do you want for Authenticated users? create/update, read, delete
次に、ストレージリソースが完全に設定されるまでデフォルトの回答の入力を続けます。
? Do you want to add a Lambda Trigger for your S3 Bucket? No Successfully added resource s33daafe54 locally
今度は、同期を維持するため設定済みのストレージリソースをバックエンドに送る必要があります。次のコマンドを実行します。
amplify push
Amplify CLI は実行される変更のステータスレポートを提供します。
✔ Successfully pulled backend environment dev from the cloud. Current Environment: dev | Category | Resource name | Operation | Provider plugin | | -------- | -------------------- | --------- | ----------------- | | Storage | s33daafe54 | Create | awscloudformation | | Auth | photogallery42b5391b | No Change | awscloudformation | ? Are you sure you want to continue? (y/n)
ストレージカテゴリが作成中であることと、前回のモジュールの設定から認証には何も変更がないことが示されます。
ストレージリソースの設定がバックエンドで完了すると、成功したことを示す出力が表示されます。
✔ All resources are updated in the cloud
-
依存関係をインストールする
次に、pubspec.yaml ファイルを Visual Studio Code で開き、ストレージプラグインを依存関係として追加します。
... # amplify_auth_cognito: '<1.0.0' amplify_storage_s3: '<1.0.0' ... # dev_dependencies:
このファイルを保存して、Visual Studio Code に Amplify Auth Cognito プラグインをインストールさせます。保存時に依存関係がインストールされない場合は、ターミナルから「$ flutter pub get」を実行することもできます。
次のような出力が得られるはずです。
exit code 0
-
プラグインを設定する
ストレージを Amplify のインスタンスにプラグインとして追加できるように、main.dart に戻ります。
... // void _configureAmplify() async { _amplify.addPlugin( authPlugins: [AmplifyAuthCognito()], storagePlugins: [AmplifyStorageS3()]); ... // try {
アプリを実行します。Amplify が今も適切に設定されストレージプラグインを含んでいることを示す成功メッセージがログに表示されるはずです。
flutter: Successfully configured Amplify 🎉
-
機能を実装する
コードを整理された状態にしておくため、ファイルのアップロードおよびダウンロード用のロジックをカプセル化する「storage_service.dart」という名前の独立したファイルを作成しましょう。以下のコードを追加します。
import 'dart:async'; import 'package:amplify_core/amplify_core.dart'; import 'package:amplify_storage_s3/amplify_storage_s3.dart'; class StorageService { // 1 final imageUrlsController = StreamController<List<String>>(); // 2 void getImages() async { try { // 3 final listOptions = S3ListOptions(accessLevel: StorageAccessLevel.private); // 4 final result = await Amplify.Storage.list(options: listOptions); // 5 final getUrlOptions = GetUrlOptions(accessLevel: StorageAccessLevel.private); // 6 final List<String> imageUrls = await Future.wait(result.items.map((item) async { final urlResult = await Amplify.Storage.getUrl(key: item.key, options: getUrlOptions); return urlResult.url; })); // 7 imageUrlsController.add(imageUrls); // 8 } catch (e) { print('Storage List error - $e'); } } }
- まず、Amazon S3 から取得される画像の URL を管理する StreamController を初期化します。
- この関数は、GalleryPage に表示する必要のある画像を取得するプロセスを開始します。
- ユーザーがアップロードした写真を表示したいだけなので、アクセスレベルを StorageAccessLevel.private に指定し、ユーザーの非公開の写真が確実に非公開のまま保持されるようにします。
- 次に、S3ListOptions で取得した関連するすべての写真をリスト化するよう、ストレージにリクエストします。
- リスト化がうまくいった場合、そのリスト化の結果が含んでいるのは実際の写真の URL ではなくキーのリストのみであるため、各写真の実際のダウンロード URL を取得する必要があります。
- .map を使用してリスト化の結果内の各アイテムを反復処理し、非同期的に各アイテムのダウンロード URL を返すようにします。
- 最後に、この URL リストをストリームに送信し、確認します。
- 途中でエラーが発生した場合は、単純にエラーを出力します。
iOS 上では、アプリが確実に画像をダウンロードできるよう Info.plist (ios > Runner > Info.plist) で App Transport Security を更新する必要があります。
... <!-- <string>Need to take pictures</string> --> <key>NSAppTransportSecurity</key> <dict> <key>NSAllowsArbitraryLoads</key> <true/> </dict> ... <!-- </dict> -->
何もアップロードしていない場合は、S3 の画像 URL リストを作成することには意味がないため、画像をアップロードする関数を追加しましょう。
// 1 void uploadImageAtPath(String imagePath) async { final imageFile = File(imagePath); // 2 final imageKey = '${DateTime.now().millisecondsSinceEpoch}.jpg'; try { // 3 final options = S3UploadFileOptions( accessLevel: StorageAccessLevel.private); // 4 await Amplify.Storage.uploadFile( local: imageFile, key: imageKey, options: options); // 5 getImages(); } catch (e) { print('upload error - $e'); } }
- これは、カメラにより提供された画像のパスを取得する非同期関数です。
- 写真が確実に一意のキーを持つようにするため、タイムスタンプをキーとして使用します。
- 前述したように、getImages を実装する際、S3 バケット内のユーザー自身のフォルダに画像がアップロードされるように、アクセスレベルを StorageAccessLevel.private に指定する必要があります。
- 次に、そのキーとファイルアップロードのオプションを指定してファイルをアップロードします。
最後に、getImages を呼び出して画像 URL の最新リストを取得し、ストリームに送信します。 - アップロードとダウンロードを正しく行うために必要なすべてのコーディングを完了しました。今度はすべてをつなぎ合わせてテストする必要があります。
まず、StreamController を引数として受け取ることでストレージから取り出された画像の URL を確認できるよう、GalleryPage を更新しましょう。
... // class GalleryPage extends StatelessWidget { final StreamController<List<String>> imageUrlsController; ... // final VoidCallback shouldLogOut; ... // final VoidCallback shouldShowCamera; GalleryPage( {Key key, this.imageUrlsController, this.shouldLogOut, this.shouldShowCamera}) : super(key: key); ... // @override
次に、単に GridView.builder の代わりに StreamBuilder を返すように _galleryGrid を更新します。
Widget _galleryGrid() { return StreamBuilder( // 1 stream: imageUrlsController.stream, builder: (context, snapshot) { // 2 if (snapshot.hasData) { // 3 if (snapshot.data.length != 0) { return GridView.builder( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2), // 4 itemCount: snapshot.data.length, itemBuilder: (context, index) { return CachedNetworkImage( imageUrl: snapshot.data[index], fit: BoxFit.cover, placeholder: (context, url) => Container( alignment: Alignment.center, child: CircularProgressIndicator()), ); }); } else { // 5 return Center(child: Text('No images to display.')); } } else { // 6 return Center(child: CircularProgressIndicator()); } }); }
- StreamBuilder は、データのスナップショットを提供するために StorageService から渡される imageUrlsController を使用します。
- この UI では、スナップショットにユーザーに関連する表示データがあることが求められます。
- また、そのデータに実際に項目があるか判定することも必要です。データがある場合、GridView の構築を続行します。
- ハードコードされた数字を使用する代わりに、スナップショットのデータの長さに基づいた GridView サイズを作成できるようになりました。
- スナップショットに何も項目がない場合、表示するものがないことを示すテキストを表示します。
今はまだグリッドの各項目のプレースホルダーが表示されています。ストリームによって提供される URL から各画像をダウンロードすることが必要になります。簡単に行えるようにするため、pubspec.yaml に新しい依存関係を追加します。
... # amplify_storage_s3: '<1.0.0' cached_network_image: ^2.3.3 ... # dev_dependencies:
このライブラリでは、URL を受け取るだけで画像をダウンロードおよびキャッシュできるウィジェットを利用できます。変更を保存し、プレースホルダーを置換できるよう GalleryPage に戻ります。
... // itemBuilder: (context, index) { return CachedNetworkImage( imageUrl: snapshot.data[index], fit: BoxFit.cover, placeholder: (context, url) => Container( alignment: Alignment.center, child: CircularProgressIndicator()), ); ... // itemBuilder closing });
プレースホルダーは CachedNetworkImage で置換されました。CachedNetworkImage はスナップショットからの URL を渡され、itemBuilder をとおしてインデックス化されます。画像を読み込む間、ウィジェットは CircularProgressIndicator を表示します。
今度は GalleryPage と CameraPage を _CameraFlowState の StorageService に接続します。まず、StorageService のインスタンスを保持するプロパティを作成します。
... // bool _shouldShowCamera = false; StorageService _storageService; ... // List<MaterialPage> get _pages {
次に、initState メソッドで _storageService を初期化します。
... // _getCamera(); _storageService = StorageService(); _storageService.getImages(); ... // initState closing }
StorageService を初期化後すぐに、アップロードされた画像が取り出されるよう getImages を呼び出すことができます。
それでは、StreamController を GalleryPage に渡しましょう。
... // child: GalleryPage( imageUrlsController: _storageService.imageUrlsController, ... // shouldLogOut: widget.shouldLogOut,
最後に、写真が撮影されたら CameraPage の didProvideImagePath の機能を更新します。
... // this._toggleCameraOpen(false); this._storageService.uploadImageAtPath(imagePath); ... // didProvideImagePath closing }
これで完了です。 アプリで写真を撮影して S3 にアップロードする準備ができました。
構築と実行をお試しください。