Edited at

Android Retrofit2 with Kotlin

実用的なライブラリをKotlin交えて紹介しようプロジェクト4

今回はAPIコールを簡単に書くことができるRetrofit2を紹介します。

ちなみに今回はJsonを自動的にクラスへ変換してくれるライブラリとしてGsonを利用します。

また、今回の実装例はこちらにPRを作っているので、よろしければ参考にどうぞ

https://github.com/HoNKoT/KotlinAndroidDatabindingSample/pull/3


Retrofit2

"A type-safe HTTP client for Android and Java"

と公式サイトに謳われているとおり、簡単にAPIコールするためのライブラリです。

また、バックグラウンドで行なってコールバックを指定することも簡単にできます。

公式サイト


使い方

使い方は至ってシンプル


  1. gradle

  2. interface実装

  3. HttpClient実装


gradle


build.gradle

compile 'com.squareup.retrofit2:retrofit:2.3.0'

compile 'com.squareup.retrofit2:converter-gson:2.3.0'
compile 'com.squareup.okhttp3:logging-interceptor:3.8.1'


interface

APIを表現、定義します。

例えば、こちらのフリーAPIを参考にさせてもらいます

https://randomuser.me/api

このとき、ここで定義するのは規定URL https://randomuser.me/ 以降の部分になります


IApiService.kt

interface IApiService {

@GET("api")
fun apiDemo(): Call<RandomUserDemo>
}

Call ... Retrofitに用意されているレシーバクラス

RandomUserDemo ... 後述する、自分で定義したModelクラス

このように、簡単にGETのAPIを定義することが可能です。

もちろん、@POST, @PUT も用意されており、簡単に定義可能です。

パラメータの渡し方、パスの作成方法など、

細かい使い方にも触れておきます


REQUEST METHOD

もっともシンプルな形

@GET("users/list")

このように直接Queryパラメータを書くことも可能です

@GET("users/list?sort=desc")

なお、Kotlinの場合、引数がなければvalのGetterとしても指定することができます

@get:GET("api")

val apiDemo: Call<RandomUserDemo>


URL MANIPULATION

例えばURL上にidが入っている場合など、

URL上に可変要素を入れたい場合などは以下のように定義します

これはGETに限らず、POST、PUTも同様です


PATH

URLのパス途中に何かを入れたい場合はこのように

{}で囲んで命名し、@Path をNameと一緒に定義することで

渡したパラメータがURLパスの中に代入されます

@GET("group/{id}/users")

fun groupList(@Path("id") groupId : Int) : Call<List<User>>


QUERY

URLの最後につく?以降の要素はこのように書きます

@GET("group/{id}/users")

fun groupList(@Path("id") groupId: Int, @Query("sort") sort: String): Call<List<User>>

これを実行すると、以下のようなURLになる

https://randomuser.me/group/3/users?sort=desc

注意:例なので、動くURLではないです


REQUEST BODY

あるインスタンスがもつ要素をkey, value変換して

APIで送りたい、というときには、そのインスタンスクラスを引数にするインターフェースを用意し、@Bodyをつけます。

@POST("users/new")

fun createUser(@Body user: User): Call<User>;

ここでのUserは、後述する自作Modelクラスであり、

どのKeyがどのインスタンス変数に該当するかというのを記載する必要がありますが、

そうすることで自動的に変換してJsonに変換して送ることができます


FORM ENCODED

変数をkey, valueで変換して送りたい場合(Form指定)は

以下のように@FormUrlEncodedをつけ、送りたいデータには@Fieldをつけます。

@FormUrlEncoded

@POST("user/edit")
fun updateUser(@Field("first_name") first: String, @Field("last_name") last: String): Call<User>

また、Multipartとの併用はできません


MULTIPART

Maltipart指定してデータを送りたい場合にも

以下のように@Multipartをつけ、送りたいデータには@Partあるいは@PartMapをつけます。

@Multipart

@PUT("user/photo")
fun updateUser(@Part("photo") photo: RequestBody, @Part("description") description: RequestBody): Call<User>

また、FormUrlEncodedとの併用はできません


HEADER MANIPULATION

ヘッダの指定もできます


静的指定

単体指定

@Headers("Cache-Control: max-age=640000")

@GET("widget/list")
fun widgetList(): Call<List<Widget>>;

複数指定

@Headers({

"Accept: application/vnd.github.v3.full+json",
"User-Agent: Retrofit-Sample-App"
})
@GET("users/{username}")
fun getUser(@Path("username") username: String): Call<User>


動的指定

@GET("user")

fun getUser(@Header("Authorization") authorization: String): Call<User>


Model

受け取り(あるいは送信)するデータのModelクラスを作成します。

今回は例として、こちらのvalueがランダムになるフリーAPIをお借りします

https://randomuser.me/api

{

"info": {
"page": 1,
"results": 1,
"seed": "7aed050e936ddbda",
"version": "1.1"
},
"results": [
{
"cell": "0486-036-722",
"dob": "1979-04-08 06:14:29",
"email": "roger.holt@example.com",
"gender": "male",
"id": {
"name": "TFN",
"value": "023064228"
},
"location": {
"city": "port macquarie",
"postcode": 1650,
"state": "western australia",
"street": "3769 dogwood ave"
},
"login": {
"md5": "e7f8bb220a82a28ad9375081f022ff2f",
"password": "aaaaaaa",
"salt": "Q6J1Te1h",
"sha1": "e2ec839f88e8fdaddbb2a83877d9afd8342bf26c",
"sha256": "a602c4c64dc6fc1b94d6c98eedbf4383d79cd8894e21cc9f37f9fca3000cea05",
"username": "greenduck262"
},
"name": {
"first": "roger",
"last": "holt",
"title": "mr"
},
"nat": "AU",
"phone": "09-2659-5113",
"picture": {
"large": "https://randomuser.me/api/portraits/men/78.jpg",
"medium": "https://randomuser.me/api/portraits/med/men/78.jpg",
"thumbnail": "https://randomuser.me/api/portraits/thumb/men/78.jpg"
},
"registered": "2004-12-21 07:19:21"
}
]
}

で、2階層目まで変換したものがこちら

それぞれのkey名が変数名に該当します

data class RandomUserDemo(var info: Info,

var results: List<Result>)

data class Info(var seed: String,
var results: Int,
var page: Int,
var version: String)

data class Result(var gender: String,
var email: String,
var registered: String,
var dob: String,
var phone: String,
var cell: String)


CUSTOM KEY NAME

Key名と変数名を違うものにしたい、という場合は

@SerializedNameをつけることで変更可能です

data class Result(var gender: String,

var email: String,
var registered: Int,
var dob: Int,
var phone: Int,
var cell: Int,
@SerializedName("id") var idMap: IdMap)

data class IdMap(var name: String,
var value: String)


TRANSIENT

逆に、key,value に変換したくない要素もあると思います。

そんなときはTransientを使いましょう。

JavaとKotlinで書き方が少し違うのでどちらも載せます

public class IdMap {

public long transient id = 0;
}

data class IdMap(@Transient var id: Long,

var name: String,
var value: String)


HttpClient

ログやヘッダー、基底URLの定義などを行い、

HttpClientを作成します

    val httpBuilder: OkHttpClient.Builder get() {

// create http client
val httpClient = OkHttpClient.Builder()
.addInterceptor(Interceptor { chain ->
val original = chain.request()

//header
val request = original.newBuilder()
.header("Accept", "application/json")
.method(original.method(), original.body())
.build()

return@Interceptor chain.proceed(request)
})
.readTimeout(30, TimeUnit.SECONDS)

// log interceptor
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
httpClient.addInterceptor(loggingInterceptor)

return httpClient
}


Retrofit instance

HttpClientを使い、Retrofitフレームワークを経由して

APIを定義したinterfaceをインスタンス化します

    // core for controller

val service: IApiService = create(IApiService::class.java)

lateinit var retrofit: Retrofit

fun <S> create(serviceClass: Class<S>): S {
val gson = GsonBuilder()
.serializeNulls()
.create()

// create retrofit
retrofit = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl("http://randomuser.me/") // Put your base URL
.client(httpBuilder.build())
.build()

return retrofit.create(serviceClass)
}

このserviceがinterfaceであり、実際にAPIコールを行う際に利用するものです


Singleton

これはアプリケーションで使い回すことをお勧めします。

Daggerを実装する場合はこのようにしましょう


AppModule.kt

    @Singleton

@Provides
internal fun providesApiService(): IApiService {
return ApiService().service
}


Execute

実行は以下のように行います


同期

try {

val response = API.apiDemo().execute()
if (response.isSuccessful()) {
return response.body()
} else {
// failed
}
} catch (e: IOException e) {
e.printStackTrace()
}


非同期

参考ソースはJavaですが、こんな感じで非同期にします。

ただお勧めとしてはRxJavaを利用すると、スッキリかけます。

API.apiDemo().enqueue(new Callback<RandomUserDemo>() {

@Override
public void onResponse(Call<RandomUserDemo> call, retrofit2.Response<RandomUserDemo> response) {
RandomUserDemo demo = response.body();
}

@Override
public void onFailure(Call<RandomUserDemo> call, Throwable t) {
}
});