retrofitは結構複雑でした。
WebAPIはhttpサーバを利用してデータを取得する方法の一つで、昔はWebサービスでxmlを利用して行っていたが、最近はWebAPIと呼び、json形式でデータをやりとりしている。jsonはxmやhtmllのように、一種のタグのようなもので表現されていると考えればいい。
今回は郵便番号を住所に変換するWebAPIを公開してくれているので、それを実装してみる。
実装にはretrofitというライブラリを使用することがgoogleから推奨されているので、今回はretrofit2とgsonというjsonをクラスに格納してくれるライブラリを使用する。RXjavaやokhttp3を利用する方法もあるが、今回は利用しない。
今回利用するWebAPIについての説明はこちら
http://zipcloud.ibsnet.co.jp/doc/api
具体的には、下記のように問い合わせると、下記のようなデータを取得できる。
json形式では、[ ]の括弧は繰り返し項目を意味する。繰り返し項目はList<T>にTとして格納される。まずはTに入る繰り返し項目から定義していく。具体的には以下のような定義になる。
次に繰り返し項目が格納される本体部分のデータクラスを作る。
説明のため順序が逆になるが、マニフェストファイルにはネットワーク通信を許可する設定をしておくと同時に、build.gradleのほうに今回利用するRetrofitとgsonのライブラリを使用するように定義しておく。
作成する関数のほうは@Path("識別する名称")としておく。そうすると関数の引数がディレクトリに入る。
http://square.github.io/retrofit/
次に受信したデータを受けるコールバックされるクラスを作成する。
Androidではアクティビティとは別のスレッドでデータ通信をしないとならない。なので、別のスレッドで処理を実行し、アクティビティのスレッドでデータだけを受け取るというような処理をすることになる。
データは返値の型が決まっているので、一つのWebAPIに対して一つのコールバック用のメソッドを作る必要があるので、アクティビティにコールバックさせるのではなく、innerクラスを作ってコールバックさせたほうが、クラスからアクティビティのメンバ変数にもアクセスできるので便利。
具体的にはretrofit2.Callback<T>を継承するクラスを作成し、それぞれメソッドをoverrideする。<T>の部分には、今回受信するデータを格納するデータクラスの型が入る。今回はZipApiDataになる。
今回は郵便番号を住所に変換するWebAPIを公開してくれているので、それを実装してみる。
実装にはretrofitというライブラリを使用することがgoogleから推奨されているので、今回はretrofit2とgsonというjsonをクラスに格納してくれるライブラリを使用する。RXjavaやokhttp3を利用する方法もあるが、今回は利用しない。
今回利用するWebAPIについての説明はこちら
http://zipcloud.ibsnet.co.jp/doc/api
具体的には、下記のように問い合わせると、下記のようなデータを取得できる。
//下記のアドレスをブラウザで表示させてみると
http://zipcloud.ibsnet.co.jp/api/search?zipcode=1000001
//下記のようなgson形式のデータが取得できる
//細かな仕様はhttp://zipcloud.ibsnet.co.jp/doc/apiを参照
{
"message": null,
"results": [
{
"address1": "東京都",
"address2": "千代田区",
"address3": "千代田",
"kana1": "トウキョウト",
"kana2": "チヨダク",
"kana3": "チヨダ",
"prefcode": "13",
"zipcode": "1000001"
}
],
"status": 200
}
retrofit2は、このjson形式のデータを取得すると、gsonというコンバータを利用して自動的にクラスにデータを格納して出力してくれる。そのため、まずはこのデータを格納するクラスを作る。json形式では、[ ]の括弧は繰り返し項目を意味する。繰り返し項目はList<T>にTとして格納される。まずはTに入る繰り返し項目から定義していく。具体的には以下のような定義になる。
data class Results(
var address1: String,
var address2: String,
var address3: String,
var kana1: String,
var kana2: String,
var kana3: String,
var prefcode: String,
var zipcode: String
)
各変数名はjsonで得られるデータ名と同じにしておく。また型もそれぞれ得られるデータに合わせて定義しておく。今回は""で囲まれているため、すべてString型になる。nullが入る可能性がある場合にはnullableとしてString?として定義する。次に繰り返し項目が格納される本体部分のデータクラスを作る。
data class ZipApiData(
var message: String?,
var results: List<Results>?,
var status: Int
)
これも変数名は同じにして型を指定する。繰り返し項目はList<T>として宣言する。今回はResultsというクラスを作成したので、List<Results>型になる。説明のため順序が逆になるが、マニフェストファイルにはネットワーク通信を許可する設定をしておくと同時に、build.gradleのほうに今回利用するRetrofitとgsonのライブラリを使用するように定義しておく。
//マニフェストの<application>タグの手前に以下のの設定をする
<uses-permission android:name="android.permission.INTERNET" />
//build.gradleに以下の記述を追加する
dependencies {
//この部分には他のライブラリが記述されているが、ここでは省略
//以下の二つを設定する
compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.retrofit2:converter-gson:2.3.0'
}
次にWebAPIのアクセス先のURLを設定したり、取得するデータなどを定義するインターフェースを作成する。よくわからないかもしれないが、アクセス先のURLを指定して、どのクラスに格納するかを指定するインターフェースとして理解しよう。
//適当な名称にインターフェースでいい
interface ZipCodeWebApiInterface {
//http://zipcloud.ibsnet.co.jp/api/search?zipcode=1000001のドメイン名以下の部分を記述する
//FQDNは別のところで指定する
@GET("api/search")
//@Queryが?zipcode=ZipCideを生成してくれる ZipCodeは関数の引数として与えられる
//返値としてWebAPIを叩きにいくサービスが生成される
//ジェネリクスには返値としてさきほど作成したデータの型を指定しておく
fun apiDemo(@Query("zipcode") ZipCode: String): Call<ZipApiData>
}
余談になるがGETだけでなくPOSTもできるし、ディレクトリ名の変更もできる。ディレクトリに挿入する場合には入れたい場所を{}でくくり、中に識別する名称を入れておく。作成する関数のほうは@Path("識別する名称")としておく。そうすると関数の引数がディレクトリに入る。
@GET("api/{zipcode}/search")
fun apiDemo(@Path("zipcode") ZipCode: String): Call<ZipApiData>
POSTの方法など詳しくは下記のページをみるといい。http://square.github.io/retrofit/
Androidではアクティビティとは別のスレッドでデータ通信をしないとならない。なので、別のスレッドで処理を実行し、アクティビティのスレッドでデータだけを受け取るというような処理をすることになる。
データは返値の型が決まっているので、一つのWebAPIに対して一つのコールバック用のメソッドを作る必要があるので、アクティビティにコールバックさせるのではなく、innerクラスを作ってコールバックさせたほうが、クラスからアクティビティのメンバ変数にもアクセスできるので便利。
具体的にはretrofit2.Callback<T>を継承するクラスを作成し、それぞれメソッドをoverrideする。<T>の部分には、今回受信するデータを格納するデータクラスの型が入る。今回はZipApiDataになる。
class MainActivity : AppCompatActivity() {
//作成するinner class以外の記述は省略
//Callback<T>を継承したクラスを作る
//innerクラスだと親クラスのメンバ変数にもアクセスできるので、MainActibity内にクラスを作ると便利
inner class ZipApiDataCallback : retrofit2.Callback<ZipApiData> {
//データ受信時に発生すると呼ばれるメソッド
override fun onResponse(call: Call<ZipApiData>?, response: Response<ZipApiData>?) {
//response.body()がZipApiDataの本体になる
//nullじゃなければログを残す
if (response?.body()?.results != null) {
if (response.body()!!.results!!.count() > 0) {
Log.v("nullpo", response.body()!!.results!![0].address1)
Log.v("nullpo", response.body()!!.results!![0].address2)
Log.v("nullpo", response.body()!!.results!![0].address3)
}
}
}
//失敗したときに呼ばれるメソッド
override fun onFailure(call: Call<ZipApiData>?, t: Throwable?) {
}
}
}
最後に実行させる部分。retrofitでサービスを作成し、それを実行するような形になる。
class MainActivity : AppCompatActivity() {
//今回は面倒なのでコンストラクタに処理を記述した
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//retrofitのインスタンスを生成して、様々な設定をする
var retrofit = Retrofit.Builder().let {
//WebAPIのアクセス先のドメインを指定する
it.baseUrl("http://zipcloud.ibsnet.co.jp/")
//受信したjson形式のデータを、kotlinのデータクラスに格納するコンバータを指定する
//今回はgoogleのgsonというコンバータのインスタンスを渡す
it.addConverterFactory(GsonConverterFactory.create())
it.build()
}
//サービスのインスタンスを生成する
//アクセス先のURLとデータクラスの型を記述したインターフェースを指定する
var service = retrofit.create(ZipCodeWebApiInterface::class.java)
//これが実行させるメソッド
//引数として郵便番号を指定する
//enqueueは非同期処理(別スレッド)で通信を行うメソッド
//引数としてコールバックさせるクラスを指定する
service.apiDemo("1000001").enqueue(ZipApiDataCallback())
}
}