【色々ハマった記録】AWS Amplify を使用してシンプルな Flutter アプリケーションを作成する

# この記事の目的

一言でいえば、**先に山に入った者の務め**をなるべく果たすということです。

Flutterを用いてフロントエンドを作り、認証機能にAWS Cognitoを使いたいと思っていました。
Flutter自体の情報が少ないので、まずは、AWSの公式のチュートリアルをやるのが良いと思いやってみました。

https://aws.amazon.com/jp/getting-started/hands-on/build-flutter-app-amplify/

チュートリアルは所要時間70分と謳われていますが、私が実際にやったら、もっと（たぶん数時間＋調査時間）が掛かりました。
AWS社への確認まではしていませんが、明らかに誤記と思われるようなところもあるので、 **先に山に入った者の務め** として、ハマりどころを記しておこうと思いました。

# チュートリアルについての補足

現時点では、このチュートリアルは日本語版と英語版とでは内容が異なるようです。
日本語版は、画像管理のアプリであるのに対して、英語版はTodoアプリになっています。

また、日本語版チュートリアルは、モジュール 1: Flutter アプリを作成してデプロイする〜モジュール 5: 分析カテゴリを追加するで構成されていますが、現時点では、私は、モジュール 3: 認証を追加するまでしか実施していません。（モジュール 4以降は、当面実施する予定はありません…）

以下、この記事は、日本語版チュートリアルを対象に記載していきます。

# 手元の環境
* OS　Ubuntu20.04
* IDE　Android Studio 4.2
* Flutter 2.0.4

Flutterを2系に上げた関係で、非推奨になっているWidgetもありますが、今回の検証の趣旨ではないため、放置しています。

# 作業後のソースコード
以下のRepositoryに上げてあります。（ご参考までに）
https://github.com/akiraabe/aws_flutter


# モジュール 1: Flutter アプリを作成してデプロイする

## ここでのポイント
特にありません。チュートリアルに準じてコーディングしていきます。

# モジュール 2: Amplify を初期化する

## ここでのポイント

まずは、「プロジェクトに依存関係を追加する」のステップで、チュートリアルでは、amplify_coreを使うように記載があります。

``` yaml
... # path:

amplify_core: '<1.0.0'

... # dev_dependencies
```
しかしながら、2020/8にamplify_flutterがリリースされているので、そちらを使う必要があるようです。
pubspec.yamlは、のdependenciesは以下のようになります。

```yaml:pubsupec.yaml
dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^0.1.2
  camera:
  path_provider:
  path:
  # amplify_core: '<1.0.0'
  amplify_flutter: '<1.0.0'
  amplify_auth_cognito: '<1.0.0'
```

次に、アプリケーションに統合するのステップでは、
チュートリアルでは、Amplifyクラスをインスタンス化して使うように記載があります。
しかし、実際にはインスタンス化するのではなく、Amplifyのstaticメソッドを直接使うようです。

```dar:main.dart
// Amplifyの設定関数のみ記載
  void _configureAmplify() async {
    Amplify.addPlugins([AmplifyAuthCognito()]);
    try {
      await Amplify.configure(amplifyconfig);
      print('Successfully configured Amplify 🎉');
    } catch (e) {
      print('Could not configure Amplify');
    }
  }

```

# モジュール 3: 認証を追加する

機能を実装するのステップで、チュートリアルでは、signUpWithCredentials関数が以下のように記載されています。

```dart:auth_service.dart
// 1
void signUpWithCredentials(SignUpCredentials credentials) async {
  try {
    // 2
    final userAttributes = {'email': credentials.email};

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

    // 4 ★この分岐が不要である
    if (result.isSignUpComplete) {
      loginWithCredentials(credentials);
    } else {
      // 5
      this._credentials = credentials;

      // 6
      final state = AuthState(authFlowStatus: AuthFlowStatus.verification);
      authStateController.add(state);
    }
  
  // 7
  } on AuthError catch (authError) {
    print('Failed to sign up - ${authError.cause}');
  }
}

```

しかし、正しくは、後段のverifyCodeと合わせて考えると、以下のようなコードが正しいと考えられます。
上記のチュートリアルの// 3のコードブロックの結果で、// 4のif文がTrueになり、loginWithCredentialsを実行します。しかし、この時点ではverifyCodeが完了していませんので、エラーとなり永遠にverifyCode画面に遷移出来ません。

```dart:auth_service.dart
  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));

        print('AuthService#signUpWithCredentials is*NOT*SignUpComplete');
        this._credentials = credentials;
        final state = AuthState(authFlowStatus: AuthFlowStatus.verification);
        authStateController.add(state);
    } on AuthException catch (authError) {
      print('Failed to sign up - ${authError.message}');
    }
  }

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

      if (result.isSignUpComplete) {
        loginWithCredentials(_credentials);
      } else {
        // Follow more steps
      }
    } on AuthException catch (e) {
      print('Could not verify code - ${e.message}');
    }
  }
```


# まとめ

Amplifyを用いると他のAWSサービスとの統合には便利ですが、当然ながら、かなりAWSに依存する形になります。
AWSに依存した部分のテスタビリティーなども少々心配です。

当面は、Amplifyを使わずに、個別にAWSのサービスと連携する方向で様子見しようかなぁと思っています。