[androidアプリ]AWSのAPIgatewayを使用してみる

## はじめに
Cognito認証を使用した各種サービスへのアクセスに非常に困惑したので備忘録として情報を記載する
特にAWSMobileClientの仕様が理解できておらず困惑したので書き留めておく．

## 何をするか
android環境で，APIgatewayに3つの状況でリクエストを送信する．
- 1.認証・認可なしの場合
    - いまここ
- 2.AWS cognito userpoolを使用する
    - [記事リンク](https://qiita.com/sanjolin_chun/items/1b37107d576a38841537)
- 3.AWS cognito userpool + idpoolを使用する
    - [記事リンク](https://qiita.com/sanjolin_chun/items/b47b1ccf536b3c4324f0)


## 環境
S3や，UIはテスト目的以外に特に使用していないので無くても動作するはずである．また，後述するが，API gatewayのandroid SDKをビルドして使用する．

-  minSdkVersion 28
- targetSdkVersion 34
- sourceCompatibility JavaVersion.VERSION_11
- targetCompatibility JavaVersion.VERSION_11
        
```build.gradle(app)
    implementation 'com.amazonaws:aws-android-sdk-core:2.72.0'
    implementation 'com.amazonaws:aws-android-sdk-apigateway-core:2.72.0'
    implementation 'com.amazonaws:aws-android-sdk-s3:2.72.0'
    implementation 'com.amazonaws:aws-android-sdk-auth-core:2.72.0'
    implementation 'com.amazonaws:aws-android-sdk-auth-ui:2.72.0'
    implementation 'com.amazonaws:aws-android-sdk-auth-userpools:2.72.0'
    implementation 'com.amazonaws:aws-android-sdk-cognitoidentityprovider:2.72.0'
    implementation 'com.amazonaws:aws-android-sdk-mobile-client:2.72.0'
```

## 手順
### 1. APIの作成

後でapigatewayのandroid SDKの使用するため，REST APIでセットアップしていく．
[API Gateway] → [APIを作成]→[REST API]から作成できる．
APIが作成できたらリソースとメソッドを作成していく．

> <em><strong> ここで気をつけることは，メソッドにANYを指定しないことである．
SDK生成において，ANYを選択した場合(少なくともandroidSDKにおいては)，メソッド/クラスそのものが生成されなくなる．</em></strong>

ここまでの設定で一旦デプロイしてしまえば，APIを叩くことができる状態になっている．[ステージの詳細]→[URLを呼び出す]に記載されたURLをブラウザにコピペで実行可能．

### 2. モデルの作成

ここまで作業が完了した後は，モデルを作成する．
モデルとは，簡単に言えば，API実行後のレスポンスの受け皿クラスである．
これはAPIのレスポンスに沿った書き方をしなければSDK側でエラーが出る．
記述方法はここでは割愛するが，一例を記す．この例は，lambdaで統合し，S3の情報を戻したものである．

モデルの作成後，[リソース]→[メソッドレスポンス]からレスポンスコードとモデルの組み合わせを設定しておく．
下の例であれば，レスポンス200に下記で作成したモデルを指定する．
改変した後は，APIをデプロイすることを忘れないこと．
```lambda.py
    try:
        response = s3_client.list_objects_v2(
            Bucket=s3_bucket_name,
            Prefix=user_directory
        )
        
        ...
        
    json_body = json.dumps(response['Contents'],default=default_encoder)
    return {
            'statusCode': 200,
            'body': json.dumps(response['Contents'], default=default_encoder)
        }
```

```response_model.json
{
  "type": "array",
  "items": {
    "type": "object",
    "properties": {
      "Key": {
        "type": "string"
      },
      "LastModified": {
        "type": "string" 
      },
      "ETag": {
        "type": "string"
      },
      "Size": {
        "type": "integer"
      },
      "StorageClass": {
        "type": "string"
      }
    }
  }
}
```

### 3. SDKのビルド

次にSDKを作成する．[ステージ]→[ステージアクション]→[SDKを生成]からSDKを作成できる．
今回はandroid向けなので，[SDKを生成]でプラットフォームの[android]を選択し．適当な値を入れる．
グループID等に適当な値を入れる．よほど変わった値を入れない限り困らないはずである．

![スクリーンショット 2024-01-16 19.19.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/574694/a13645f8-88af-e308-90af-9c545ecd1291.png)


実行後，SDKファイルがダウンロードされる．詳細に関しては，ダウンロードしたファイルのREADMEに記載してあるが，ダウンロードしたSDKは，ビルドが必要である．
SDKのreadmeでは，ローカルにmavenのビルド環境を入れるように催促されるが，java側のバージョンに合ったdockerでビルドしても問題はない．

今回は，ダウンロードされたものの解凍フォルダと，下記のdockerfileを```[user]/Work/```直下に展開して，shファイルを実行するだけでビルドしてくれるようにした．
```_docke_run.sh
docker build  -t apigatewaybuilder .
docker run -v /Users/[user]/Work/:/usr/src/app --name android_apigateway_sdk_builder -d -it apigatewaybuilder /bin/bash
docker cp android_apigateway_sdk_builder:/app/[SDKファイルネーム]/target ./

docker stop android_apigateway_sdk_builder
docker rm android_apigateway_sdk_builder
docker rmi apigatewaybuilder

```

```dockerfile
# Dockerfile

# MavenとOpenJDK 11をベースにしたイメージを使用
FROM maven:3.6.3-openjdk-11

# ホストのディレクトリをコピー
COPY . /app

# SDKディレクトリに移動
WORKDIR /app/[SDKファイルネーム]

# installと書いているが，buildでも問題ないはず．．．
# Mavenを使用してプロジェクトをビルド
RUN mvn clean install

# Dockerイメージ実行時のデフォルトコマンドを設定
CMD ["mvn", "package"]
```

実行後，```[user]/Work/target/```直下に生成されたjarファイルを```[プロジェクト名]/[app]/libs/```に配置すれば良い．android studioでは，目視できないフォルダなので，他の手段で配置すること．また，appはデフォルト名なので，名前を変えている場合は変更する必要がある．libsが存在しない場合は，作成すること．

androidプロジェクト側に先程作成した.jarを認識してもらう必要があるため，build.gradleに以下を記述する．
```build.gradle(app)
    implementation fileTree(dir: 'libs', include: '*.jar')
    implementation files('libs/[jarファイル名]')
```

### 4. Android デバイスで実行する
使い方に関しては，下記の通り．手元の環境でAPI名にハイフン(-)が混じっている場合は，省略されることは確認できている．

```.java
ApiClientFactory factory = new ApiClientFactory();

final ApinameClient client = factory.buid(apinameClient.class);
// final [API名 + Client] client = factory.build([API名 + Client.class]);

final ApiResponseModel model = client.userBackupTestGet();
// final [モデル名] model = client.[APIのリソースを繋げたもの+アクション名(GET/POST等)]();
```

上記のコードのmodelには，APIgatewayで指定した型通りのオブジェクトが含まれている．SDKを利用して最も良い点は，モバイル側でリクエストやレスポンスの制御をしなくて良いことで，少ない行数でAPIへのリクエストが完了することである．

> <em><strong> リクエストに関しては，メインスレッドで実行すると，リクエストが実行されずエラーが戻るので注意すること．</em></strong>

メインスレッドで実行すると下記のようなエラーが出る．
```.log
java.lang.RuntimeException: Unable to start activity ComponentInfo

...


Caused by: android.os.NetworkOnMainThreadException at
android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1667)
```