【Developer Preview】Amplify Flutterを試してみた

湯婆婆が画面いっぱいに出てきてわあとなりました。
今日はうって変わってFlutter Webは非対応ですのでご了承ください。

# まえがきのまえがき
2021-2-18にAmplify FlutterがGAされました。[公式ブログ](https://aws.amazon.com/jp/blogs/news/amplify-flutter-is-now-generally-available-build-beautiful-cross-platform-apps/)
GA時点では大きく機能追加されたわけではなく、Preview版の修正や小規模な機能追加が中心と見られるため、
まだそれほど古くなったものではないと思いたいです。。。
が、Preview版時点での情報を記載していますので、その辺は悪しからず。


# まえがき
今年の7月から10月くらいにかけて、仕事でFlutterをゼロから勉強してやってました。（すなわち今回久しぶりにリハビリする・・・）
そんな縁もあって、初めてこういう企画に参加しようかなって思ったら、表側にはmonoさんがいたりと、Googlingでお世話になった人もいて恐縮です。まあ、できる限り情報提供できるように頑張ってみます。

表題の通り、Amplify Flutterをやってみようと思います。

# 目標
この記事では、Amplify公式の[Getting Started](https://docs.amplify.aws/start/q/integration/flutter)から初めて、ごくごくあり得そうな構成を実際に作ってみようと思いま~~す~~した。

# Amplify Flutterについて簡単に説明
## Amplifyとは
正式名称は「AWS Amplify」。[公式ページ](https://aws.amazon.com/jp/amplify/)の説明を借りると、以下です。
>AWS Amplify は、モバイルとウェブのフロントエンドデベロッパーが、安全でスケーラブルなフルスタックアプリケーションを構築しデプロイできるようにする、AWS による製品およびツールのセットです。

ということで、主語がフロントエンドデベロッパーとあるように、フロントエンド開発するときに、いい感じにAWS環境（バックエンド）まで作ってくれるツール、が本来のAmplifyです。ただ、もちろん既に構築済みのバックエンドに対するAWS謹製のフロントエンドライブラリとしても利用できます。

体感ですが、そこそこの規模のtoB,toC向けシステムなら、後者になりそうな気がします。Amplifyが絡まない部分の管理どうすんねんっていう話がありますからね。。。

## Amplify Flutterとは
これまで、Amplifyは[このページ](https://docs.amplify.aws/start)にあるように色んなフロントエンドフレームワークに対応していましたが、2020年8月、ついに[Flutterにも対応](https://aws.amazon.com/jp/about-aws/whats-new/2020/08/amplify-flutter-now-available-as-developer-preview/)しました。ただし、Developer Previewなので、本番環境には使うなということです。

ちなみに、じゃあ今までどうしてたのかというと、大半の人はpub.devで調べてサードパーティのものを使うなり、場合によってはMethodChannelを駆使しつつ、ネイティブのものを使って自作、なんてことをする必要があったのかもしれません。


# 設計と実装
何はともあれ、ちゃんとPreviewしないとねってことで、つべこべ言わずに~~早速~~発表から3ヶ月経ってるのに今更やってみます。

## Todoリスト
はい。最後は諦めました。が、半日くらいでそこそこのレベルにはなってくれました。
- [x] チュートリアル通りにAmazon PinPointと疎通できる
- [x] サインアップできる
- [x] サインインできる
- [x] サインアウトできる
- [ ] REST APIで疎通できる(Amplify非対応)
- [ ] S3にファイルをアップロードできる

---
- [x] サインイン状態を維持できる 

## 成果物リンク

- リポジトリhttps://github.com/taniyuu/amplify_flutter_sample
- デモ（サインイン/サインアウト）↓
![20201208004658.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/416137/b1c08256-e505-b34f-ea8b-9b85d6298c62.gif)



## 手順（抜粋）
### チュートリアルページを進める
https://docs.amplify.aws/start/getting-started/setup/q/integration/flutter

**[Set up fullstack project](https://docs.amplify.aws/start/getting-started/setup/q/integration/flutter)**

- Amplify CLIは、現時点では不要です。
- (iOSのみ)Podfileを11.0に上げるよう指示されますが、`flutter create`時にはPodfileは生成されません。`pubspec.yaml`に追加した時点でPodfileも追加されるので、その時点で調整しましょう。

**[Integrate in your app](https://docs.amplify.aws/start/getting-started/integrate/q/integration/flutter)**

- `amplifyconfiguration.dart`が何か全然わからないので、ファイルは作らずに飛ばしましょう（Amplify CLIで決めることはわかっていつつ)。
  - なんなら、AnalyzeとAuthを勝手にimportさせられている(自己責任)

**[Connect to the cloud](https://docs.amplify.aws/start/getting-started/add-api/q/integration/flutter)**

ここで、満を辞してAmplify CLIの登場です。CLIのセットアップはそれだけで記事になるレベルなので、こちらを参照ください。
[AWS Amplify CLIの使い方〜インストールから初期セットアップまで〜](https://qiita.com/Junpei_Takagi/items/f2bc567761880471fd54)


ちなみに、古いとそもそもCLIでFlutter選べないのでご注意。

```
yuki@taniyamayuukinoMacBook-Air amplify_flutter_sample % amplify --version

   ╭─────────────────────────────────────────────╮
   │                                             │
   │      Update available 4.29.3 → 4.37.1       │
   │   Run npm i -g @aws-amplify/cli to update   │
   │                                             │
   ╰─────────────────────────────────────────────╯

4.29.3
yuki@taniyamayuukinoMacBook-Air amplify_flutter_sample %
```

アップデートすると出てきました。

```
yuki@taniyamayuukinoMacBook-Air amplify_flutter_sample % amplify init             
Initializing new Amplify CLI version...
Done initializing new version.
Scanning for plugins...
Plugin scan successful
Note: It is recommended to run this command from the root of your app directory
? Enter a name for the project amplifyfluttersample
? Enter a name for the environment dev
? Choose your default editor: Visual Studio Code
? Choose the type of app that you're building (Use arrow keys)
  android 
❯ flutter 
  ios 
  javascript 
```

念押しされますが気にせず進みましょう。

```
⚠️  Flutter project support in the Amplify CLI is in DEVELOPER PREVIEW.
Only the following categories are supported:
 * Auth
 * Analytics (Amazon Pinpoint only)
 * API (GraphQL only)
 * Storage
```

デプロイに成功すると、このようになります。

- Flutter側
    - amplifyconfiguration.dartができています。さっき飛ばしたのが正解だったとここで確信します。
    - (お断り) amplify/配下は、環境値が入っているので今回のリポジトリからはごめんなさいします。

    ![スクリーンショット 2020-12-06 18.54.47.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/416137/949166ea-34a0-6998-33cb-8ccb9f9a48ea.jpeg)

- バックエンド側
![スクリーンショット 2020-12-06 19.11.06.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/416137/b0f4b6ee-f9b4-4a77-6454-8250cf06de22.jpeg)

ちなみに、この時点でiOSで`flutter run`すると、このようなログが出ます。
`MethodChannel`を呼び出しているのが意図せず~~（割と意図してました）~~確認できました。

```
[VERBOSE-2:ui_dart_state.cc(177)] Unhandled Exception: PlatformException(AmplifyException, Failed to Configure Amplify, The operation couldn’t be completed. (Amplify.PluginError error 2.), null)
#0      StandardMethodCodec.decodeEnvelope (package:flutter/src/services/message_codecs.dart:582:7)
#1      MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:159:18)
<asynchronous suspension>
#2      MethodChannel.invokeMethod (package:flutter/src/services/platform_channel.dart:332:12)
#3      MethodChannelAmplifyCore.configure (package:amplify_core_plugin_interface/method_channel_amplify.dart:25:21)
#4      Amplify.configure (package:amplify_core/amplify_core.dart:78:35)
#5      _MyHomePageState._configureAmplify (package:amplify_flutter_sample/main.dart:78:27)
#6      _MyHomePageState.initState (package:amplify_flutter_sample/main.dart:65:5)
#7      StatefulElement._firstBuild (package:flutter/src/widgets/framework.dart:4765:58)
#8      ComponentElement.mount (package:flutter<…>
```

長くなりましたが、あとは手順に従いAnalyticsを導入します。
（東京リージョンでAmplifyの設定は行ったのに、Amazon Pinpointはオレゴンリージョンにできました...）

すると、変数`_amplifyConfigured`が`true`になって、イベント記録ができるようになったようです。
![スクリーンショット 2020-12-06 20.59.43.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/416137/664c89ce-8430-ee00-067a-6fc377c026c5.jpeg)

`record event`ボタンをポチッとすると・・・記録されてますね。
![スクリーンショット 2020-12-06 21.09.43.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/416137/63ddae16-ca14-b357-d7fa-56c3fd87ad97.jpeg)


**[Next steps](https://docs.amplify.aws/start/getting-started/nextsteps/q/integration/flutter)**
完成！ここまでは順調。でした。

### Authentication
https://docs.amplify.aws/lib/auth/getting-started/q/platform/flutter
Cognito User Poolを使って認証してみます。
が、チュートリアルでId Pool使ってるので、アップデートになる…
なお、`Default configuration`にも2つあるので、今回は`without Social Provider`にしました。（ソーシャル連携は、それはそれで別記事が1つできます）

```
yuki@taniyamayuukinoMacBook-Air amplify_flutter_sample % amplify update auth
Please note that certain attributes may not be overwritten if you choose to use defaults settings.

You have configured resources that might depend on this Cognito resource.  Updating this Cognito resource could have unintended side effects.

Using service: Cognito, provided by: awscloudformation
 What do you want to do? Apply default configuration without Social Provider (Federation)
```

さて、続いてログインのUIは…

なんと自作を要求されました。。。

JavaScript系は[Use pre-built UI components](https://docs.amplify.aws/lib/auth/getting-started/q/platform/js#option-1-use-pre-built-ui-components)でAmplifyがUIを提供してくれていたので、ここはかなりがっかりでした。
まあ、仕方なく作りました。

ユーザ名とパスワードでサインアップ、サインインできるかなと推測してやっても、

```
flutter: Unrecognized auth error returned from platform. See logs for details
```
とdetailsの行方がわからず、とりあえずユーザプールの設定を見に行くことにしました。


#### Cognitoユーザプールの設定確認
Eメールアドレスが必須になっていました。[^1] サインアップ時は、このアドレスを確認することで登録完了とするようで、その画面実装がマストになってしまいました。
[^1]: [こちらのページ](https://docs.amplify.aws/lib/auth/signin/q/platform/flutter#register-a-user)で、emailを入力するように記載はされていましたが、気づきませんでした。

![スクリーンショット 2020-12-07 0.39.09.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/416137/fc65cbd8-cc4c-fc6e-f613-0848ef3ed9ce.jpeg)

ということで、サインアップ、確認コード入力ページ、サインインページを実装し…たところ、思わぬ事象に当たりました。
![スクリーンショット 2020-12-07 7.20.44.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/416137/7ed8a8b0-e9c5-0177-e39e-0b2b7f66b664.jpeg)
サインインの戻りに、認可情報（IDトークンなど）がついてこない…
REST APIは、未対応なのでスクラッチでDartでメソッド作って内につけたいのに…っていうか、ユーザの属性照会画面とか、「Hello,〇〇さん」とかでユーザ名欲しい場面あるっしょ。

#### 【解消済み】ログインしたユーザの属性が取れない
下記記載の事象は[このPR](https://github.com/aws-amplify/amplify-flutter/pull/343)によって解消されました。
現在はDocも更新されているので、それに従ってください。

-----
2020年12月現在[こちら](https://docs.amplify.aws/lib/auth/user-attributes/q/platform/flutter#fetch-the-current-users-attributes)をみると、

>Fetch the current user’s attributes
Invoke the following api to get the list of attributes assigned to the user.

>This functionality has not yet been implemented for Flutter, this section will be updated once it has been added.

ギョエェェ、、、

仕方なくIssueを調べると、WorkAroundが出てきました。
https://github.com/aws-amplify/amplify-flutter/issues/201#issuecomment-730822991

```dart


final authState = await Amplify.Auth.fetchAuthSession(
        options: CognitoSessionOptions(getAWSCredentials: true))
    as CognitoAuthSession;
if (authState.isSignedIn) {
  final claims = _parseJwt(authState.userPoolTokens.idToken);
  final email = claims['email'] as String;
}
```
`fetchAuthSession`でセッション情報を取得し、`idToken`(JWT)をパースし`claim`を取得し、そこから属性を取得…という、かなり（主観では）ゴリゴリな実装を求められました。
`fetchAuthSession`は、[こちら](https://github.com/aws-amplify/amplify-flutter/issues/186#issuecomment-726889372)にあるように、トークンリフレッシュも期限切れのタイミングでよしなにやってくれる便利メソッドです。

何はともあれ、これを使って、最初のデモにあるようにHello (ユーザ名)を出せるようになりました。
（見た目上は画面遷移で渡してるだろって思われそうですが、再起動しても同様に表示できていることから、トークンの情報を使えているということでお許しください。）


# ちょっと考察
途中で`MethodChannel.invokeMethod`のスタックトレースを出してみたりしましたが、結局はAmplify FlutterはiOS版とAndroid版をラップしたインタフェースを実装している段階です。
下記はAWS公式のブログより拝借したものですが、将来的にはDart onlyにして、WebもDesktopもやるぞという心意気だそうです。
![](https://d2908q01vomqb2.cloudfront.net/0a57cb53ba59c46fc4b692527a38a87c78d84028/2020/08/19/AmplifyFlutterDesignArchitecture.jpg)

ということは、プロダクションレディになるのはその段階なのか？とも思える感覚は、今回Authの実装ひとつとっても持てました。推測にはなりますが、少なくともAuthに関してはかなりの部分がDart onlyで実装できそうですし、APIもその気になれば`package:http`でクライアントはできちゃいそうですし、はてどれだけこれから需要が出てくるのかな？（反語?）っていうのは気になるところでした。

ただ、この思想にはいいところもあって、ちゃんとiOS, Androidのネイティブライブラリ、SDKを遺憾無く使ってくれる[^2] のは良かったです。今回のサンプルコード、あえて状態管理を超適当にできたのも、認証情報をネイティブ側に持っていってくれてたからですし。Flutterプラグインサードパーティーばっかりなのもな…っていうとっかかりに悩む人にも門戸を広げるきっかけになってくれることを期待はしたいです。

[^2]: iOSは認証情報をデフォルトで[KeyChainに入れてくれてます](https://docs.amplify.aws/sdk/auth/how-it-works/q/platform/ios#offline-support)

# まとめ
Amplify Flutterで、バックエンドの実装をすることなく認証機能を作ることができました。
個人的に作業ログを振り返ると、圧倒的Amplify力不足で申し訳なかったですが、とはいえ結構詰まりそうな所を踏んで解決したという自信も無駄に出てきました。

~~とりあえず、本番環境では使うな、じゃなくてまだ見た目もよちよち赤ちゃんレベルなので、使う気もあんまり起こらないとは思います（こら）が、それくらいFlutterもといDartは、AWSエンジニアの一部の心を揺さぶってるんだろうなとか想像しました。そしてこのリポジトリ、結構これから険しい道が待っていそうな気がしていますが、頑張って欲しいですね。~~
→GAされたので、今以上に発展していくことを願って協力していきたいですね。

ということで乱文になってしまいましたが、ありがとうございました。
明日は @muttsu-623 さんです。


# 参考記事
日本語に限ると、2020/12初旬時点ではこんな感じです。

- [Amplify Flutterを学ぶ](https://qiita.com/makotomi/items/7bb09ff225d0fcbce1b6)
  - Authのスニペット付き
- [【速報】Amplify Flutterがリリースされました！（プレビュー）](https://dev.classmethod.jp/articles/what-new-amplify-flutter/)
  - AWSブログを翻訳してくれている
- [AWSのハンズオン](https://aws.amazon.com/jp/getting-started/hands-on/build-flutter-app-amplify/)
    - 機械翻訳？RxDartを使っているので、その経験がないとちょっと難しいかもしれない（自分含む）。