AndroidでAppSync

[DMM.com Advent Calendar 2018](https://qiita.com/advent-calendar/2018/dmm) 9日目の記事です。

動画サービスのAndroid系の動画プレイヤーを開発しているエンジニアで、@pinkienortです。今年2018年に入社し、Android開発を初めて約5ヶ月になりました。

ひょんなこと(↓の理由)から、AppSyncのを使うことがあったので、Androidでの使い方をざっくり紹介したいと思います。


### 経緯: Amazonアプリの公開で困った

DMMの動画サービスでは、動画視聴プレイヤーとして数多くのでデバイをサポートとしています。Android/iOS系のスマホや、レグザやブラビアなどテレビ、PS4などなどあります。

[マルチデバイス対応でもっと便利に！ - DMM.com](https://www.dmm.com/multi_device/)

そのデバイスの1つAmazon FireTVもあります。そんなFireTV向けのアプリを公開するときに、次のエラーが出くわしました。

> Caution: Google Cloud Messaging Detected

(えー。gcm使っちゃだめなのー。)

調べたところ、、、

![SS 2018-12-09 at 13.46.34.png](https://qiita-image-store.s3.amazonaws.com/0/117186/1e2f77cc-a0a6-90dc-7c3c-51165ef4f8b2.png)

> [Migrating An Existing App to the Amazon Appstore | Appstore Submission](https://developer.amazon.com/docs/app-submission/migrate-existing-app.html#APIsandServices)

↑の理由から、Amazonアプリストアでアプリを公開するときに、ワーニングが表示され、審査が思うどおりに進まない！ということがあったので、AppSyncを使うことになりました。


## 本題: AndroidでAppSync

AppSyncはAWSの1つで、GraphQLでAPIを記述し、バックエンドとしてLambdaやDynamoDBなどに接続できるようになってます。今回はDynamoDBを想定して進めていきます。

#### API/GraphQL作成

では、実際に作っていきましょー。
AWSのコンソールからAppSyncのダッシュボードに移動します。次にCreate APIをクリックすると、作成画面になります。今回はBuild from scratchを選択します。

![screenshot_AWS AppSync Console / Create API](https://qiita-image-store.s3.amazonaws.com/0/117186/cba8e9d1-5690-08bd-b789-1e4f94174df9.png)

これでAPIが作成できました！、、、が、肝心のAPIを呼び出すGraphQLがありません！なので、データを取得するQueryやデータを作成するMutationやデータそのものを定義するスキーマを作成していきます。
左側のメニューからSchemaを選択すると、何も定義されてないスキーマファイルが出てきます。
Create ResourcesからDyanamoDBのテーブルとスキーマの両方を作成します。

![screenshot_AWS AppSync Console / Schema](https://qiita-image-store.s3.amazonaws.com/0/117186/fa90e76c-6273-9b96-982e-8e42413c5c59.png)

ここでは、データ型を１つ定義する(例では`MyCustomType`)と、それに対応するテーブルが作成できます。さらに、そのテーブルにアクセスするためのGraphQLが自動生成されます(Query, Mutation, Subscription)。

![screenshot_AWS AppSync Console / Create Resources](https://qiita-image-store.s3.amazonaws.com/0/117186/11a70a2d-1baf-0eb5-3c86-cb470fb1d416.png)

ページ下部にあるCreateを押すと、定義したデータ型に対応するDynamoDBのテーブルと、そのデータ型のMutationやQuery, Subscriptionも自動生成されます。

![screenshot_AWS AppSync Console - Schema, GraphQL](https://qiita-image-store.s3.amazonaws.com/0/117186/1bc2af1e-ece5-b668-0b9f-e5428f20263f.png)

#### Android Proejctのセットアップ

セットアップを始める前に、configファイルをダウンロードします。
左メニューから、名前をつけたAPIのアイテムをクリックすると、Getting startedが表示されます。そして、Download Configurationから`awsconfiguration.json`をダウンロードして、`./app/src/main/res/raw`に置いておきます。

AppSyncのコンソールの指示に沿って、AWS Amplifyの設定をします。

AmplifyがWeb上からAppSyncの設定データを取得するために、Amplify用のユーザを作成するように言われるので、そのまま従ってください。
ユーザ作成が完了したら、Webに表示される`accessKeyId`と`secretAccessKey`をコンソールに入力して、Enterを押して進めばユーザが作成されます。

```shell

npm install -g @aws-amplify/cli
cd <Android Projectのrootディレクトリ>
amplify init
```

GraphQLをAndroid側で使用できるようにするため、コード生成します。このときに、AppSyncのWEBコンソールで作成したSchemaを元にGraphQLのクエリ(Schemaファイルにある`type Query`でない）が自動で生成されます。

```sh

amplify add codegen --apiId <API_ID>
```

最後に、Androidのビルドコンフィグにライブラリを追加すれば、セットアップ完了です。


```gradle:build.gradle

    classpath 'com.amazonaws:aws-android-sdk-appsync-gradle-plugin:2.6.+'
```

```gradle:app/build.gradle

    apply plugin: 'com.android.application'
    apply plugin: 'com.amazonaws.appsync'
    android {
        // Typical items
    }
    dependencies {
        // Typical dependencies
        implementation 'com.amazonaws:aws-android-sdk-appsync:2.6.+'
        implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.0'
        implementation 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.1'
    }
```

#### Androidで動かす

ようやく疎通チャレンジです。

まず、AppSyncのクライアントインスタンスを生成します。認証やリージョンなどの設定として、ダウンロードしたコンフィグファイルを読み込ませます。

```kotlin:initAppSyncClient

import com.amazonaws.mobileconnectors.appsync.AWSAppSyncClient
import com.amazonaws.mobile.config.AWSConfiguration
    lateinit var appSyncClient: AWSAppSyncClient

    private fun initAppSyncClient() {
        appSyncClient = AWSAppSyncClient.builder()
                .context(applicationContext)
                .awsConfiguration(AWSConfiguration(applicationContext))
                .build()
    }
```

クライアントの生成後に、`query`を実行してデータを取得します(`builder`では、`limit`や`filter`で取得数を制限したり、で特定のものだけ取ってきたりするカスタマイズがデフォルトで生成されています)。エラーがでなければ、ログに取得したデータが吐き出されます。`response.data`に`nextToken`も入ってるので、追いロードもできます。

```kotlin:query
import com.apollographql.apollo.exception.ApolloException
import com.amazonaws.amplify.generated.graphql.ListMyCustomTypesQuery
import com.apollographql.apollo.GraphQLCall
import com.amazonaws.mobileconnectors.appsync.fetcher.AppSyncResponseFetchers
import com.apollographql.apollo.api.Response
import android.util.Log

    private fun query() {
        appSyncClient.query(ListMyCustomTypesQuery.builder().build())
                .responseFetcher(AppSyncResponseFetchers.CACHE_AND_NETWORK)
                .enqueue(listMyCustomTypeCallback)
    }

    private val listMyCustomTypeCallback = object : GraphQLCall.Callback<ListMyCustomTypesQuery.Data>() {
        override fun onResponse(response: Response<ListMyCustomTypesQuery.Data>) {
            Log.i("Results", response.data()?.listMyCustomTypes()?.items()?.mapNotNull {
                it -> it.content()
            }?.joinToString())
        }

        override fun onFailure(e: ApolloException) {
            Log.e("ERROR", e.toString())
        }
    }
```


`query`だけではデータ作成していないため、空データが返却されます。
`mutation`でデータを作成しましょう。データ作成できれば、`id`が出力されます。

```kotlin:mutation
import com.apollographql.apollo.exception.ApolloException
import com.amazonaws.amplify.generated.graphql.CreateMyCustomTypeMutation
import com.apollographql.apollo.GraphQLCall
import com.amazonaws.mobileconnectors.appsync.fetcher.AppSyncResponseFetchers
import com.apollographql.apollo.api.Response
import android.util.Log
import type.CreateMyCustomTypeInput

    private fun mutation() {
        appSyncClient.mutate(CreateMyCustomTypeMutation.builder().input(
                CreateMyCustomTypeInput.builder()
                        .content("cola")
                        .price(120)
                        .title("My custom Title")
                        .build())
                .build())
                .enqueue(createMyCustomTypeCallback)
    }

    private val createMyCustomTypeCallback = object : GraphQLCall.Callback<CreateMyCustomTypeMutation.Data>() {
        override fun onResponse(response: Response<CreateMyCustomTypeMutation.Data>) {
            Log.i("Results", response.data()?.createMyCustomType()?.id())
        }

        override fun onFailure(e: ApolloException) {
            Log.e("ERROR", e.toString())
        }
    }
}
```

## おわりに

今回紹介では、AppSyncでAPIを作成してAndroidから動かしてみる！でした。AppSyncはバックエンドとしてDynamoDB以外にもLambdaがあり、認証方法もAPI_KEYだけでなく、CognitoやIAM、OpenIDも利用できます。

次はそちらも紹介できればと思います。

### 参考URL

- [Building a Client App](https://docs.aws.amazon.com/appsync/latest/devguide/building-a-client-app.html#aws-appsync-building-a-client-app)
- [AWS Amplify Android SDK API](https://aws-amplify.github.io/docs/android/api)
- [Migrating An Existing App to the Amazon Appstore](https://developer.amazon.com/docs/app-submission/migrate-existing-app.html#APIsandServices)