🏙️

Flutter x AWS Amplifyでアプリを作る

2021/12/22に公開

この記事はFlutter Advent Calendar(カレンダー2)の22日目の記事です。


普段私はFlutterでアプリを開発する際はFirebaseと組み合わせてアプリを開発するのですが、
今回はAWSのAmplifyと組み合わせてアプリを作る方法をAWSのチュートリアルを参照しながら実装してみます。
https://aws.amazon.com/jp/getting-started/hands-on/build-flutter-app-amplify/

※チュートリアルでのFlutterはバージョンが古いので2系に書き直す必要があります。

作るもの

フォトギャラリーアプリ

今回利用するAmplifyの機能

  • Amplify Auth (Cognito) ...ログイン認証
  • Amplify Storage (S3) ...ストレージ
  • Amplify Analytics (Pinpoint) ...ログ分析

作成する環境

zsh
% flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 2.5.3, on macOS 11.6 20G165 darwin-arm, locale ja-JP)
[✓] Android toolchain - develop for Android devices (Android SDK version 31.0.0)
[✓] Xcode - develop for iOS and macOS
[✓] Chrome - develop for the web
[✓] Android Studio (version 2020.3)
[✓] VS Code (version 1.63.0)
[✓] Connected device (1 available)

Amplifyとは

公式サイトでは以下のように記載されています。

AWS Amplify は、フロントエンドのウェブ/モバイルデベロッパーが AWS でフルスタックアプリケーションをすばやく簡単に構築できるようにする専用のツールとサービスのセットであり、広範な AWS のサービスを活用してさらにアプリケーションをカスタマイズする柔軟性を備えています。

一言でいうと、AWSをmBaaSとして扱うためのプラットフォームだと思って良いかと思います。
クロスプラットフォームのフレームワークにも対応しており、FlutterはもちろんReact NativeやIonicも対応してるみたいです。

Amplifyの機能

公式サイトを見ると、Firebaseとほぼ同等の機能が使えるように見えます。

Amplifyのセットアップ

1. Amplify CLIのインストール

zsh
% npm install -g @aws-amplify/cli@flutter-preview
zsh
% amplify --version
7.7.0-flutter-preview.1

2. Amplifyの設定

amplify configureコマンドでAWSがブラウザ上で起動し、IAMユーザーの設定画面が表示されるので最後までデフォルトで進んで、アクセスキーとシークレットキーを取得。
今回は以下の内容で作成

region: ap-northeast-1
user name: amplify-flutter
accessKeyId: IAMユーザー(amplify-flutter)のアクセスキー
secretAccessKey: IAMユーザー(amplify-flutter)のシークレットキー
Profile Name: default

zsh
% amplify configure
Follow these steps to set up access to your AWS account:

Sign in to your AWS administrator account:
https://console.aws.amazon.com/
Press Enter to continue

Specify the AWS Region
? region:  ap-northeast-1
Specify the username of the new IAM user:
? user name:  amplify-flutter
Complete the user creation using the AWS console

Press Enter to continue

Enter the access key of the newly created user:
? accessKeyId:  ********************
? secretAccessKey:  ****************************************
This would update/create the AWS Profile in your local machine
? Profile Name:  default

Successfully set up the new user.

3. Amplifyのプロジェクト初期化

まずはFlutterアプリのプロジェクトのあるディレクトリに移動

zsh
% cd path/to/your/project

初期化コマンドのamplify initを実行
今回は以下の内容で作成

project: flutteramplify
Initialize the project with the above configuration: Yes
Select the authentication method you want to use: AWS access keys
accessKeyId: IAMユーザー(amplify-flutter)のアクセスキー
secretAccessKey: IAMユーザー(amplify-flutter)のシークレットキー
region: ap-northeast-1

zsh
% amplify init
Note: It is recommended to run this command from the root of your app directory
? Enter a name for the project flutteramplify
The following configuration will be applied:

Project information
| Name: flutteramplify
| Environment: dev
| Default editor: Visual Studio Code
| App type: flutter
| Configuration file location: ./lib/

? Initialize the project with the above configuration? Yes
Using default provider  awscloudformation
? Select the authentication method you want to use: AWS access keys
? accessKeyId:  ********************
? secretAccessKey:  ****************************************
? region:  ap-northeast-1
Adding backend environment dev to AWS Amplify app: d3lihq3zh80btx
⠼ Initializing project in the cloud...

...(略)

✔ Successfully created initial AWS cloud resources for deployments.
✔ Initialized provider successfully.
Initialized your environment successfully.

Your project has been successfully initialized and connected to the cloud!

成功するとプロジェクトのあるディレクトリ直下にamplifyというフォルダが作成されます。

※Githubのパブリックリポジトリなどで管理するときの注意!
.gitignoreに、ある程度amplifyフォルダ内のファイルを除外する設定が書き込まれますが、ignoreには下記の2行を追加しておくことをお勧めします。

.gitignore
amplify/
**/amplifyconfiguration.dart

また、AWSのAmplifyにアクセスするとflutteramplifyが作成されてます。

flutteramplifyをクリックするとdev環境がデプロイされてることが確認できます。

FlutterでAmplifyの接続設定

さて、ここからFlutter側のコーディングを行なっていきます。

amplify_flutterプラグインを利用します
https://pub.dev/packages/amplify_flutter

チュートリアルではamplify_coreのプラグインを使っていますが、amplify_flutterを利用します

pubspec.yaml
dependencies:
  amplify_flutter: ^0.2.10

先程のamplify initでlibの直下にamplifyconfiguration.dartが作成されており、amplifyconfigを読み込む設定を行います。

Amplifyインスタンスの生成し、amplifyconfiguration.dartのamplifyconfigを読み込む。

main.dart
final _amplify = Amplify;

Future<void> _configureAmplify() async {
  try {
    await _amplify.configure(amplifyconfig);
    debugPrint('Successfully configured Amplify 🎉');
  } catch (e) {
    debugPrint(e.toString());
  }
}

Amplifyの認証を使ってみる

Amplifyの設定

次にAmplifyのAuthを使えるようにamplify add authを実行します。
今回は以下の内容で作成

configuration: Default configuration
sign in: Username
configure advanced settings: No, I am done.

zsh
% amplify add auth
Using service: Cognito, provided by: awscloudformation
 
 The current configured provider is Amazon Cognito. 
 
 Do you want to use the default authentication and security configuration? Default configuration
 Warning: you will not be able to edit these selections. 
 How do you want users to be able to sign in? Username
 Do you want to configure advanced settings? No, I am done.
✅ Successfully added auth resource flutteramplifya947e592 locally

成功するとamplify/backend/backend-config.jsonファイルにauthの設定内容が書き込まれます。

amplify pushでバックエンドに反映

zsh
% amplify push
✔ Successfully pulled backend environment dev from the cloud.

    Current Environment: dev
    
┌──────────┬────────────────────────┬───────────┬───────────────────┐
│ Category │ Resource name          │ Operation │ Provider plugin   │
├──────────┼────────────────────────┼───────────┼───────────────────┤
│ Auth     │ flutteramplifya947e592 │ Create    │ awscloudformation │
└──────────┴────────────────────────┴───────────┴───────────────────┘
? Are you sure you want to continue? Yes

...(略)

⠦ Updating resources in the cloud. This may take a few minutes...

UPDATE_COMPLETE amplify-flutteramplify-dev-02235 AWS::CloudFormation::Stack Sun Dec 12 2021 00:49:32 GMT+0900 (Japan Standard Time) 
✔ All resources are updated in the cloud

FlutterでAmplifyの認証を実装する

amplify_auth_cognitoプラグインを利用します
https://pub.dev/packages/amplify_auth_cognito

pubspec.yaml
dependencies:
  amplify_flutter: ^0.2.10
  # Add
  amplify_auth_cognito: ^0.2.10

先ほどのamplifyconfigの読み込む処理の前にaddPluginsでAmplifyAuthCognitoを追加します。

main.dart
final _amplify = Amplify;

Future<void> _configureAmplify() async {
  try {
    // プラグイン読み込みを追加
    await _amplify.addPlugins([AmplifyAuthCognito()]); 
    await _amplify.configure(amplifyconfig);
    debugPrint('Successfully configured Amplify 🎉');
  } catch (e) {
    debugPrint(e.toString());
  }
}

※プラグインの追加方法がパッケージのバージョンアップに伴いチュートリアルとは異なり、複数のプラグインを指定を指定できるのはaddPluginではなくaddPluginsを利用するみたいです。今回はアプリを作るのに3つのプラグインを利用するので、addPluginsに変更してます。

サインアップ処理の実装

チュートリアルにあるようにauth_service.dartを用意し、signUpWithCredentialsを実装します。
Amplify.Auth.signUpを使ってAmplifyのAuthでサインアップ処理を行います。

auth_service.dart
Future<void> signUpWithCredentials(SignUpCredentials credentials) async {
  try {
    final userAttributes = {'email': credentials.email};

    final result = await Amplify.Auth.signUp(
      username: credentials.username,
      password: credentials.password,
      options: CognitoSignUpOptions(userAttributes: userAttributes));

    if (result.isSignUpComplete) {
        // サインアップ後の処理
    } else {
        // サインアップ未完了
    }
  } on AuthException catch (authError) {
    debugPrint('Failed to sign up - ${authError.message}');
  }
}

次にメールアドレスに飛ばした認証コードを認証するverifyCodeを実装します。Amplify.Auth.confirmSignUpを使って認証処理を行います。

auth_service.dart
Future<void> verifyCode(String verificationCode) async {
  try {
    final result = await Amplify.Auth.confirmSignUp(
      username: _credentials.username, confirmationCode: verificationCode);

    if (result.isSignUpComplete) {
        // サインアップ後の処理
    } else {
        // サインアップ未完了
    }
  } on AuthException catch (authError) {
    debugPrint('Could not verify code - ${authError.message}');
  }
}

サインイン処理の実装

次にサインインを実装していきます。
Amplify.Auth.signInを使ってサインイン処理を行います。

auth_service.dart
Future<void> loginWithCredentials(AuthCredentials credentials) async {
  try {
    final result = await Amplify.Auth.signIn(
      username: credentials.username, password: credentials.password);

    if (result.isSignedIn) {
      // サインイン後の処理
    } else {
      // サインイン失敗
    }
  } on AuthException catch (authError) {
    debugPrint('Could not login - ${authError.message}');
  }
}

サインアウト処理の実装

最後にサインアウトを実装していきます。
Amplify.Auth.signOutを使ってサインアウト処理を行います。

auth_service.dart
Future<void> logOut() async {
  try {
    await Amplify.Auth.signOut();
    // サインアウト後の処理
  } on AuthException catch (authError) {
    debugPrint('Could not log out - ${authError.message}');
  }
}

これで一連の認証まわりの実装ができました。
チュートリアルに沿って画面を作って繋ぎこめば「サインアップ→認証コード→サインイン→サインアウト」の流れが確認できます。

Amplifyのストレージを使ってみる

Amplifyの設定

次にAmplifyのStorageを使えるようにamplify add storageを実行します。
今回は以下の内容で作成

services: (Images, audio, video, etc.)
access: Auth users only
kind of access: create/update, read, delete
add a Lambda Trigger: no

zsh
% amplify add storage
? Select from one of the below mentioned services: Content (Images, audio, video, etc.)
✔ Provide a friendly name for your resource that will be used to label this category in the project: · s3a5a3ee9c
✔ Provide bucket name: · flutteramplify4a4f3f91ac6c4a12a6d790a94488e22d
✔ Who should have access: · Auth users only
✔ 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? (y/N) · no
✅ Successfully added resource s3a5a3ee9c locally

⚠️ If a user is part of a user pool group, run "amplify update storage" to enable IAM group policies for CRUD operations
✅ Some next steps:
"amplify push" builds all of your local backend resources and provisions them in the cloud
"amplify publish" builds all of your local backend and front-end resources (if you added hosting category) and provisions them in the cloud

成功するとamplify/backend/backend-config.jsonファイルにstorageの設定内容が書き込まれます。

amplify pushでバックエンドに反映

zsh
% amplify push
✔ Successfully pulled backend environment dev from the cloud.

    Current Environment: dev
    
┌──────────┬────────────────────────┬───────────┬───────────────────┐
│ Category │ Resource name          │ Operation │ Provider plugin   │
├──────────┼────────────────────────┼───────────┼───────────────────┤
│ Storage  │ s3a5a3ee9c             │ Create    │ awscloudformation │
├──────────┼────────────────────────┼───────────┼───────────────────┤
│ Auth     │ flutteramplifya947e592 │ No Change │ awscloudformation │
└──────────┴────────────────────────┴───────────┴───────────────────┘
? Are you sure you want to continue? Yes

...(略)

⠼ Updating resources in the cloud. This may take a few minutes...

✔ All resources are updated in the cloud

Amplify Studio上から見ると以下のようにデプロイされています。

FlutterでAmplifyのストレージを実装する

amplify_storage_s3プラグインを利用します
https://pub.dev/packages/amplify_storage_s3

pubspec.yaml
dependencies:
  amplify_flutter: ^0.2.10
  amplify_auth_cognito: ^0.2.10
  # Add
  amplify_storage_s3: ^0.2.10

Authの時と同様にaddPluginsでAmplifyStorageS3を追加します。

main.dart
final _amplify = Amplify;

Future<void> _configureAmplify() async {
  try {
    // プラグイン読み込みを追加
    await _amplify.addPlugins([AmplifyAuthCognito(), AmplifyStorageS3()]); 
    await _amplify.configure(amplifyconfig);
    debugPrint('Successfully configured Amplify 🎉');
  } catch (e) {
    debugPrint(e.toString());
  }
}

ストレージのファイルリストの読み込みとファイル取得処理の実装

チュートリアルにあるようにstorage_service.dartを用意し、getImagesを実装します。
Amplify.Storage.listを使ってAmplifyのStorageでファイルのリスト取得して、Amplify.Storage.getUrlでファイルのURLを取得します。

storage_service.dart
Future<void> getImages() async {
  try {
    final listOptions =
      S3ListOptions(accessLevel: StorageAccessLevel.private);

    final result = await Amplify.Storage.list(options: listOptions);

    final getUrlOptions =
      GetUrlOptions(accessLevel: StorageAccessLevel.private);

    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;
    }));

    imageUrlsController.add(imageUrls);
  } catch (e) {
    debugPrint('Storage List error - $e');
  }
}

ストレージに画像ファイルをアップロードする処理の実装

次に画像のアップロード処理を実装していきます。
Amplify.Storage.uploadFileを使ってアップロード処理を行います。
S3UploadFileOptionsでストレージのアクセスレベルをprivateに設定しておきます。

storage_service.dart
Future<void> uploadImageAtPath(String imagePath) async {
  final imageFile = File(imagePath);
  final imageKey = '${DateTime.now().millisecondsSinceEpoch}.jpg';

  try {
    final options =
      S3UploadFileOptions(accessLevel: StorageAccessLevel.private);

    await Amplify.Storage.uploadFile(
      local: imageFile, key: imageKey, options: options);

    getImages();
  } catch (e) {
    debugPrint('upload error - $e');
  }
}

チュートリアルに沿って画面を作って繋ぎこめば「ギャラリー画面でストレージのファイル取得→撮影画面からストレージへのアップロード」の流れが確認できます。
わりと簡単にストレージも実装できました。

AmplifyのAnalyticsを使ってみる

Amplifyの設定

最後にAmplifyのAnalyticsを使ってログを収集してみます。
Analyticsを使えるようにamplify add analyticsを実行します。
今回は以下の内容で作成

an Analytics provider: Amazon Pinpoint
access: Auth users only
allow guests and unauthenticated users to send analytics events: Yes

zsh
% amplify add analytics
? Select an Analytics provider Amazon Pinpoint
? Provide your pinpoint resource name: flutteramplify
Auth configuration is required to allow unauthenticated users, but it is not configured properly.
Adding analytics would add the Auth category to the project if not already added.
? Apps need authorization to send analytics events. Do you want to allow guests and unauthenticated users to send analyt
ics events? (we recommend you allow this when getting started) Yes
✅ Successfully updated auth resource locally.
Successfully added resource flutteramplify locally

Some next steps:
"amplify push" builds all of your local backend resources and provisions them in the cloud
"amplify publish" builds all your local backend and front-end resources (if you have hosting category added) and provisions them in the cloud

成功するとamplify/backend/backend-config.jsonファイルにanalyticsの設定内容が書き込まれます。

amplify pushでバックエンドに反映

zsh
% amplify push
✔ Successfully pulled backend environment dev from the cloud.

    Current Environment: dev
    
┌───────────┬────────────────────────┬───────────┬───────────────────┐
│ Category  │ Resource name          │ Operation │ Provider plugin   │
├───────────┼────────────────────────┼───────────┼───────────────────┤
│ Analytics │ flutteramplify         │ Create    │ awscloudformation │
├───────────┼────────────────────────┼───────────┼───────────────────┤
│ Auth      │ flutteramplifya947e592 │ Update    │ awscloudformation │
├───────────┼────────────────────────┼───────────┼───────────────────┤
│ Storage   │ s3a5a3ee9c             │ No Change │ awscloudformation │
└───────────┴────────────────────────┴───────────┴───────────────────┘
? Are you sure you want to continue? Yes

...(略)

⠼ Updating resources in the cloud. This may take a few minutes...

✔ All resources are updated in the cloud

Pinpoint URL to track events https://ap-northeast-1.console.aws.amazon.com/pinpoint/home/?region=ap-northeast-1#/apps/****************/analytics/overview

amplify pushの最後にPinpointのURLが表示されており、そこにアクセスすると、Pinpointの分析ダッシュボードにアクセスできます。

FlutterでAmplifyのAnalyticsを実装する

amplify_analytics_pinpointプラグインを利用します
https://pub.dev/packages/amplify_analytics_pinpoint

pubspec.yaml
dependencies:
  amplify_flutter: ^0.2.10
  amplify_auth_cognito: ^0.2.10
  amplify_storage_s3: ^0.2.10
  # Add
  amplify_analytics_pinpoint: ^0.2.10

Authの時と同様にaddPluginsでAmplifyAnalyticsPinpointを追加します。

main.dart
final _amplify = Amplify;

Future<void> _configureAmplify() async {
  try {
    // プラグイン読み込みを追加
    await _amplify.addPlugins([
      AmplifyAuthCognito(),
      AmplifyStorageS3(),
      AmplifyAnalyticsPinpoint(),
    ]);
    await _amplify.configure(amplifyconfig);
    debugPrint('Successfully configured Amplify 🎉');
  } catch (e) {
    debugPrint(e.toString());
  }
}

イベントの定義を実装

分析する特定のイベントの場所で呼び出すためのイベント内容を一つのファイルに定義します。

analytics_events.dart
abstract class AbstractAnalyticsEvent {
  AbstractAnalyticsEvent.withName({required String eventName})
      : value = AnalyticsEvent(eventName);
  AbstractAnalyticsEvent.withEvent({required AnalyticsEvent event})
      : value = event;

  final AnalyticsEvent value;
}

class LoginEvent extends AbstractAnalyticsEvent {
  LoginEvent() : super.withName(eventName: 'login');
}

class SignUpEvent extends AbstractAnalyticsEvent {
  SignUpEvent() : super.withName(eventName: 'sign_up');
}

class VerificationEvent extends AbstractAnalyticsEvent {
  VerificationEvent() : super.withName(eventName: 'verification');
}

class ViewGalleryEvent extends AbstractAnalyticsEvent {
  ViewGalleryEvent() : super.withName(eventName: 'view_gallery');
}

class TakePictureEvent extends AbstractAnalyticsEvent {
  factory TakePictureEvent({required String cameraDirection}) {
    final event = AnalyticsEvent('take_picture');
    event.properties.addStringProperty('camera_direction', cameraDirection);
    return TakePictureEvent._fromEvent(event);
  }

  TakePictureEvent._fromEvent(AnalyticsEvent event)
      : super.withEvent(event: event);
}

ログを出力する処理の実装

チュートリアルにあるようにanalytics_service.dartを用意し、logを実装します。
引数に先ほど実装したanalytics_events.dartのAbstractAnalyticsEventを渡し、
Amplify.Analytics.recordEventを使ってAmplifyのAnalyticsでログを収集します。

analytics_service.dart
class AnalyticsService {
  static void log(AbstractAnalyticsEvent event) {
    Amplify.Analytics.recordEvent(event: event.value);
  }
}

ログを取りたいところにAnalyticsを埋め込む

ログインボタン押下時にログを収集するためにAnalyticsService.log(LoginEvent())を埋め込みます。

login_page.dart
  void _login() {
    final username = _usernameController.text.trim();
    final password = _passwordController.text.trim();

    final credentials =
        LoginCredentials(username: username, password: password);
    widget.didProvideCredentials(credentials);
    
    // ログ収集
    AnalyticsService.log(LoginEvent());
  }

ログの確認

アプリを実行し、いくつか記録されるようにアプリを動かしたら下記のamplify consoleでPinpointダッシュボードを開きます。

今回は以下の内容で作成

site: Amplify Studio

zsh
 % amplify console
? Which site do you want to open? … 
❯ Amplify Studio
  AWS console

https://ap-northeast-1.admin.amplifyapp.com/admin/**********/dev/home

Amplify StudioでAnalyticsを開くと、デプロイしたリソースが表示されているので、それをクリックします。

Pinpointが別タブで立ち上がり、「分析 > イベント」をクリックするとイベントが収集されてることが確認できます。

ここまでがチュートリアルでやれることです。

(おまけ)AmplifyとFirebaseとの比較

参考記事
https://existek.com/blog/aws-amplify-vs-firebase-whats-better/

下記の画像で気になったのは、costproject size
Firebaseの方がコスト削減向けのようで、プロジェクトサイズに関してはAmplifyは中規模の企業やエンタープライズ向けに対し、Firebaseは小規模の企業も対象になっています。
中小企業やスタートアップのような小さくアプリを作って検証したい時はFirebase。
エンタープライズのような要件が複雑になりそうなものはAmplifyや裏側のAWSを駆使するみたいな構成がいいのかなと感じました。

AmplifyとFirebaseでそれぞれ裏側で利用されてるサービスの一覧

最後に

今回チュートリアルで作ったソース

ソース載せときます。
https://github.com/unbam/flutter_amplify

Discussion