実用的なライブラリを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コールするためのライブラリです。
また、バックグラウンドで行なってコールバックを指定することも簡単にできます。
使い方
使い方は至ってシンプル
- gradle
- interface実装
- HttpClient実装
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/
以降の部分になります
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を実装する場合はこのようにしましょう
@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) {
}
});