Amazon API Gateway の API のクライアント SDK を生成して Android アプリから呼び出す

API Gateway

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

API のクライアントの SDK を生成して呼び出す

Amazon API Gateway (以下 API Gateway) はマネージドな API を簡単に作成することが出来る素晴らしいサービスです。

API Gateway の機能はそれだけに留まらず、API クライアントの SDK を iOS や Android、Javascript 向けに生成する機能も持っています。これを使うと非常に簡単に API クライアントまでも実装することができます。

今回はこの機能を使って、API Gateway で作成した API のクライアントを Android 向けに生成し、Android アプリに組み込んで呼び出すところまでをやってみたいと思います。

先日、同じような内容の iOS 版を投稿しましたが、こちらと同じ Model で行います。こちらを既にお読みいただいている場合は「SDK の生成」まで読み飛ばしてください。

API の作成

今回は OpenWeatherMap の Weather API をラップした API を作り、この API のクライアントを作ります。API の作成手順は以下のブログにまとめてありますので、こちらの手順通りに作成してください。

Model の作成

API のレスポンスをクライアントで受け取るには、API のレスポンスボディとして渡される JSON オブジェクトをパースする必要がありますが、API Gateway には Model を定義しておくことにより、その Model のクラスを各種 SDK 向けに生成する機能があります。この Model クラスも含めて、API クライアントの SDK としてまとめて生成することができます。

ということで Model を作成します。API Gateway のヘッダーのメニューの一番右側のドロップダウンから「Models」を選択します。

api-gateway-ios01

左側に表示されているのは Model 一覧です。デフォルトで ErrorEmpty というモデルが作成されています。ここに Weather API のレスポンスの Model を作成しましょう。「Create」をクリックします。

api-gateway-ios02

右側に新規作成フォームが表示されます。Model name は Model の名前を入力します。ここでは Weather とします。Content type はレスポンスボディとして返すときのフォーマットで、レスポンスヘッダの Content-Type になります。通常、application/json で良いでしょう。Model description は任意項目ですので、必須ではありません。

api-gateway-ios03

一番大事な設定が、一番下にある Model schema です。ここには Model の定義を JSON Schema 形式で記述します。JSON Schema は、JSON のデータ構造を記述するための書式のことです。JSON のデータに含まれる値のデータ型や説明などを記述することができます。日本語では、以下の記事が詳しいです。

ということで、Model schema を用意する必要があります。といっても一から自力で記述する必要はありません。以下のツールを使うと、実際の JSON データから JSON Schema を生成することができます。

左上の JSON に実際の JSON データを入れて「Generate Schema」をクリックすると右側に JSON Schema が生成されます。Force required がデフォルトで有効になっていますが、今回の Model 作成には不要なので無効にしておきます。

api-gateway-ios04

OpenWeatherMap の Weather API の実際のレスポンスボディは以下の様な感じです。これをインプットとして JSON Schema を生成します。

{
  "coord": {
    "lon": 139.69,
    "lat": 35.69
  },
  "weather": [
    {
      "id": 800,
      "main": "Clear",
      "description": "Sky is Clear",
      "icon": "01n"
    }
  ],
  "base": "cmc stations",
  "main": {
    "temp": 298.43,
    "pressure": 1011,
    "humidity": 83,
    "temp_min": 294.82,
    "temp_max": 300.93
  },
  "wind": {
    "speed": 5.7,
    "deg": 190
  },
  "clouds": {
    "all": 0
  },
  "dt": 1436613100,
  "sys": {
    "type": 1,
    "id": 7619,
    "message": 0.0048,
    "country": "JP",
    "sunrise": 1436556845,
    "sunset": 1436608744
  },
  "id": 1850147,
  "name": "Tokyo",
  "cod": 200
}

JSON Schema に変換すると、以下のようになりました。ちょっと長くなってしまってすみません…。

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "id": "/",
  "type": "object",
  "properties": {
    "coord": {
      "id": "coord",
      "type": "object",
      "properties": {
        "lon": {
          "id": "lon",
          "type": "number"
        },
        "lat": {
          "id": "lat",
          "type": "number"
        }
      }
    },
    "weather": {
      "id": "weather",
      "type": "array",
      "items": {
        "id": "0",
        "type": "object",
        "properties": {
          "id": {
            "id": "id",
            "type": "integer"
          },
          "main": {
            "id": "main",
            "type": "string"
          },
          "description": {
            "id": "description",
            "type": "string"
          },
          "icon": {
            "id": "icon",
            "type": "string"
          }
        }
      }
    },
    "base": {
      "id": "base",
      "type": "string"
    },
    "main": {
      "id": "main",
      "type": "object",
      "properties": {
        "temp": {
          "id": "temp",
          "type": "number"
        },
        "pressure": {
          "id": "pressure",
          "type": "integer"
        },
        "humidity": {
          "id": "humidity",
          "type": "integer"
        },
        "temp_min": {
          "id": "temp_min",
          "type": "number"
        },
        "temp_max": {
          "id": "temp_max",
          "type": "number"
        }
      }
    },
    "visibility": {
      "id": "visibility",
      "type": "integer"
    },
    "wind": {
      "id": "wind",
      "type": "object",
      "properties": {
        "speed": {
          "id": "speed",
          "type": "number"
        },
        "deg": {
          "id": "deg",
          "type": "integer"
        }
      }
    },
    "clouds": {
      "id": "clouds",
      "type": "object",
      "properties": {
        "all": {
          "id": "all",
          "type": "integer"
        }
      }
    },
    "dt": {
      "id": "dt",
      "type": "integer"
    },
    "sys": {
      "id": "sys",
      "type": "object",
      "properties": {
        "type": {
          "id": "type",
          "type": "integer"
        },
        "id": {
          "id": "id",
          "type": "integer"
        },
        "message": {
          "id": "message",
          "type": "number"
        },
        "country": {
          "id": "country",
          "type": "string"
        },
        "sunrise": {
          "id": "sunrise",
          "type": "integer"
        },
        "sunset": {
          "id": "sunset",
          "type": "integer"
        }
      }
    },
    "id": {
      "id": "id",
      "type": "integer"
    },
    "name": {
      "id": "name",
      "type": "string"
    },
    "cod": {
      "id": "cod",
      "type": "integer"
    }
  }
}

API Gateway の設定画面に戻り、Model schema に貼り付けて「Create model」をクリックして終わりです。

api-gateway-ios05

Method Response の設定

Model が出来上がったら、レスポンスに設定します。Method Execution 画面に移動して Method Response をクリックします。

api-gateway-ios06

HTTP Status200 が表示されているので、これを展開すると Response Models for 200 セクションの中に一つだけ Model が設定されていると思います。デフォルトでは Empty が設定されているので、これを Weather に変更します。最後にチェックマークをクリックして終わりです。

api-gateway-ios07

Method Response の設定が終わったら、デプロイを行います。「Deploy API」をクリックします。Method Execution から選ぶとデプロイできないバグがあるので、Resource 一覧 または Method 一覧から行ってください(分かりやすいバグなので、きっと近いうちに直ると思います)。

api-gateway-ios08

SDK の生成

デプロイが終わったらいよいよ SDK を生成します。デプロイが完了すると Stage の 設定画面が表示されていると思いますので、SDK Generation タブをクリックします。

gateway-android01

Platform は iOS、Android、JavaScript から選べます。今回は Android を選択します。その下の各設定は Android プロジェクトに組み込むときに必要な内容になります。これらの設定値は pom.xml で使用されます。

設定値 説明
Group ID SDK の作成者の名前空間 jp.classmethod.sample
Invoker package SDK のパッケージ名 jp.classmethod.sample.gateway
Artifact ID SDK の名前 gateway
Artifact version SDK のバージョン 1.0.0

ここまでできたら「Generate SDK」をクリックし、Zip ファイルをダウンロードします。

Android プロジェクトへの組み込み

次に、生成した SDK を Android プロジェクトに組み込みましょう。なお、Android プロジェクトは作成済みの前提で進めさせていただきますので、Android Studio で作成しておいてください。

まず build.gradle に API Gateway の SDK を追加します。

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.2.0'
    compile 'com.amazonaws:aws-android-sdk-apigateway-core:2.2.3'
}

Gradle の Sync を実行し、SDK を使える状態にしておきましょう。

次に生成した SDK を組み込みます。ダウンロードした Zip ファイルを解凍すると次のようなフォルダ構成になっています。

gateway-android02

このフォルダ以下で mvn install コマンドを実行しましょう。すると target フォルダの中に jar ファイルが生成できるはずです。

$ mvn install
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building apigateway 1.0.0
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ apigateway ---

...中略

[INFO] Installing /Users/yuki/Downloads/apigateway-1.0.0/target/apigateway-1.0.0.jar to /Users/yuki/.m2/repository/jp/classmethod/sample/apigateway/1.0.0/apigateway-1.0.0.jar
[INFO] Installing /Users/yuki/Downloads/apigateway-1.0.0/pom.xml to /Users/yuki/.m2/repository/jp/classmethod/sample/apigateway/1.0.0/apigateway-1.0.0.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 01:38 min
[INFO] Finished at: 2015-07-12T11:07:59+09:00
[INFO] Final Memory: 20M/112M
[INFO] ------------------------------------------------------------------------

mvn コマンドを実行するには、Apache Maven をインストールする必要があります。Homebrew を使ってインストールするのが一番簡単ですので、インストールしてから実行しましょう。

$ brew install maven
==> Downloading https://www.apache.org/dyn/closer.cgi?path=maven/maven-3/3.3.1/binaries/apache-maven-3.3.1-bin.tar.gz
==> Best Mirror http://ftp.riken.jp/net/apache/maven/maven-3/3.3.1/binaries/apache-maven-3.3.1-bin.tar.gz
######################################################################## 100.0%
🍺  /usr/local/Cellar/maven/3.3.1: 92 files, 9.3M, built in 6 seconds

こうして生成した jar ファイルを app/libs フォルダに入れて、導入完了です。

gateway-android03

ここで、生成したクラスを軽く覗いてみましょう。Model のルートにあたる Weather クラスは以下のような感じです。普通のエンティティクラスですね。

/*
 * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file is distributed
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

package jp.classmethod.sample.apigateway.model;

import jp.classmethod.sample.apigateway.model.WeatherClouds;
import jp.classmethod.sample.apigateway.model.WeatherWind;
import jp.classmethod.sample.apigateway.model.WeatherWeatherItem;
import jp.classmethod.sample.apigateway.model.WeatherMain;
import java.util.*;
import jp.classmethod.sample.apigateway.model.WeatherCoord;
import jp.classmethod.sample.apigateway.model.WeatherSys;

public class Weather {
    private WeatherCoord coord = null;
    private List<WeatherWeatherItem> weather = new ArrayList<WeatherWeatherItem>() ;
    private String base = null;
    private WeatherMain main = null;
    private Integer visibility = null;
    private WeatherWind wind = null;
    private WeatherClouds clouds = null;
    private Integer dt = null;
    private WeatherSys sys = null;
    private Integer id = null;
    private String name = null;
    private Integer cod = null;

    /**
     * Gets coord
     *
     * @return coord
     **/
    public WeatherCoord getCoord() {
        return coord;
    }

    /**
     * Sets the value of coord.
     *
     * @param coord the new value
     */
    public void setCoord(WeatherCoord coord) {
        this.coord = coord;
    }

    /**
     * Gets weather
     *
     * @return weather
     **/
    public List<WeatherWeatherItem> getWeather() {
        return weather;
    }

    /**
     * Sets the value of weather.
     *
     * @param weather the new value
     */
    public void setWeather(List<WeatherWeatherItem> weather) {
        this.weather = weather;
    }
    
    // ...中略

}

API を呼び出してみる

それでは呼び出してみましょう。まず忘れずに AndroidManifest.xml にインターネット接続権限を書きましょう。

<uses-permission android:name="android.permission.INTERNET" />

API を呼び出すには WeatherAPIClient クラスを使います(このように、クライアントのクラスは XXXAPIClient という形式で生成されます)。このクラスのオブジェクトは ApiClientFactory クラスのオブジェクトの build() メソッドで生成できます。生成した WeatherAPIClientweatherGet() メソッドを呼び出すと、HTTP リクエストが実行されます。

package sample.classmethod.jp.apigatewaysample;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.Toast;

import com.amazonaws.mobileconnectors.apigateway.ApiClientFactory;

import jp.classmethod.sample.apigateway.WeatherAPIClient;
import jp.classmethod.sample.apigateway.model.Weather;
import jp.classmethod.sample.apigateway.model.WeatherWeatherItem;


public class MainActivity extends AppCompatActivity {

    final MainActivity self = this;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 非同期スレッドで実行
        new Thread(new Runnable() {
            @Override
            public void run() {
                invoke();
            }
        }).start();
    }

    void invoke() {
        WeatherAPIClient client = new ApiClientFactory().build(WeatherAPIClient.class);
        Weather weather = client.weatherGet("Toyko");
        WeatherWeatherItem item = weather.getWeather().get(0);
        final String message = "Weather : " + item.getMain();
        
        // メインスレッドでAlertDialogを表示
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                new AlertDialog.Builder(self)
                        .setTitle("Weather")
                        .setMessage(message)
                        .setPositiveButton(android.R.string.ok, null)
                        .create()
                        .show();
            }
        });
    }

}

実行してみましょう。レスポンスを受け取ることができました!

gateway-android04

まとめ

生成された SDK の使いかたは、AWS Mobile SDK とほぼ変わらないので、触れたことがあるかたは使いやすいものとなっています。API Gateway で作られる API は AWS の特別なものではないので、HTTP クライアントに別なライブラリ (AFNetworking とか) を使用している場合は SDK の生成機能を必ずしも使う必要はありません。

なお、今回は Public な API として設定しましたが、この方法以外に API キーを利用する方法や IAM を利用する方法などもあります。これらの利用方法は別途ご紹介できればと思います。

参考