Let's はじめてのRetrofit for Android in Kotlin（サンプルコード付き）

# はじめに
これまでOkHttpでサーバとのHTTP通信を行っていたのですが、最近はRetrofitが流行っているじゃないですか。
この記事ではそんなRetrofitの使い方を説明します。

# 動作環境
この記事の動作環境は以下のとおりです。

Android Studio：4.1
Kotln：1.4.10
Open JDK:1.8
compileSdkVersion：30
targetSdkVersion：30
minSdkVersion：16

# 目標
以下を目標とします。

 * RetrofitでHTTP通信
 * 文字列としてのJSONを取得する
 * JSON取得時にGsonでコンバートする
 * Retrofitの非同期処理の実装方法

#事前準備
本サンプルで使うサーバは[Node](https://nodejs.org/ja/)の[JSON](https://github.com/typicode/json-server) Serverを利用しています。
サンプルプロジェクトを動作させるには、事前に用意しておいてください。

# アプリの概要
[Retrofit](https://square.github.io/retrofit/)を利用して、JSON ServerにGET、POST、PUT、DELETEでHTTP通信するアプリです。
基本的なRESTサーバにアクセスする機能を持ちます。

# Retrofitのイメージ
そもそもRetrofitってどうやって動いているのかを理解しなければ、ソースコードを確認しても理解が難しいと思います。

![Retrofitの概要.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99960/7b5a647e-622e-b41a-db23-057c522f5b2b.png)

登場人物としては以下です。

* Service（インターフェイス）
* Retrofit
* Service（実装クラス）
* Callクラス

大きな処理の流れとしては以下です。

1. はじめに、Service（インターフェイス）を定義しておきます。<br>どのようなパスにどのようなHTTPメソッドでアクセスするかを定義します。
2. RetrofitクラスをBuilderクラスを通してインスタンスを取得します。<br>インスタンス取得の際にベースとなるURL(http://xxx.xxx.xxx/)を設定します。
3. RetrofitクラスからService（インターフェイス）の実装クラスを生成します。
4. Service（実装クラス）からCallクラスを取得します。
5. CallクラスでHTTP通信を実施します。


# JSON文字列でWebAPIにHTTP通信する実装方法
はじめに、文字列として用意したJSONのデータを送信し、受け取るJSONも文字列として取得する実装方法を説明します。

## 実行イメージ

実行イメージは以下のようになります。
<img width="30%" alt="ファイル名" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99960/c8af4056-9bc2-7aa7-4f3f-9356630d32b8.gif">


## サンプルコード
[GitHub](https://github.com/ktaro-nakaya/qiita-retrofit)からダウンロードしてください。

## GETメソッドでHTTP通信する実装方法


### コード解説

#### build.gradle(app)への追記

はじめに、Retrofitのライブラリーをdependenciesに追記します。

```groovy:build.gradle(app)
dependencies {
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
}

```

#### Serviceクラスの定義

サーバに接続するURLのパスやHTTPメソッド、送信するパラメータなどを抽象メソッドとして定義します。

```kotlin:MyService.kt
package jp.co.casareal.retrofitbasic

import okhttp3.RequestBody
import okhttp3.ResponseBody
import retrofit2.Call
import retrofit2.http.*

interface MyService{

    @GET("posts")
    fun getRawResponseForPosts(): Call<ResponseBody>

    @POST("posts")
    fun postRawRequestForPosts(@Body body:RequestBody):Call<ResponseBody>

    @PUT("posts/{id}")
    fun putRawRequestForPosts(@Path("id") id:String, @Body body:RequestBody):Call<ResponseBody>

    @DELETE("posts/{id}")
    fun deletePathParam(@Path("id") id:String ):Call<ResponseBody>
}
```
メソッドや引数などにアノテーションを付与することにより様々な設定ができます。

メソッドに付与できるアノテーション

|アノテーション|引数|説明|
|:----|:----|:----|
|@GET|value(String)|引数にパスの設定や送信するパスパラムのパラメータのプレースホルダを{}で記述できます。<br>引数に@Urlが存在する場合は引数はオプション扱いになります。|
|@POST|value(String)|引数にパスの設定や送信するパスパラムのパラメータのプレースホルダを{}で記述できます。<br>引数に@Urlが存在する場合は引数はオプション扱いになります。|
|@PUT|value(String)|引数にパスの設定や送信するパスパラムのパラメータのプレースホルダを{}で記述できます。<br>引数に@Urlが存在する場合は引数はオプション扱いになります。|
|@DELETE|value(String)|引数にパスの設定や送信するパスパラムのパラメータのプレースホルダを{}で記述できます。<br>引数に@Urlが存在する場合は引数はオプション扱いになります。|
|@Multipart|なし|マルチパートで送信する場合に付与します。<br>@POSTなどと併せてせっていします。|
|@Header|value(String Array)|HTTP Headerを設定できます。単一の場合はString型にします。複数ある場合は、文字列配列で付与します。|

メソッドの引数に設定できるアノテーション

|アノテーション|引数|説明|
|:----|:----|:----|
|@Path|value(String)<br>encoded(boolean)|{}で設定したプレースホルダに値を設定します。文字列には{}で指定した文字列を指定します。booleanには設定する値がエンコード済みかどうかを指定します。(デフォルトfalse)|
|@Query|value(String)<br>encoded(boolean)|クエリパラメータのキーをStringで指定します。booleanには設定する値がエンコード済みかどうかを指定します。(デフォルトfalse)|
|@QueryMap|Map<br>encoded(boolean)|クエリパラメータが複数存在する時に利用できます。クエリーパラメータをキーと値のMapで渡します。booleanには設定する値がエンコード済みかどうかを指定します。(デフォルトfalse)|
|@QueryName|value(String)|クエリーパラメータでキーのみを設定できます。|
|@Field|value(String)|HTTPリクエストメッセージボディに付与する値のキーをStringとして指定します。|
|@FieldMap|Map<br>encoded(boolean)|HTTPリクエストメッセージボディに付与する値が複数存在する場合に利用できます。booleanには設定する値がエンコード済みかどうかを指定します。(デフォルトfalse)|
|@Part|value(String)<br>encoding(String)|マルチパートで送信するデータのキーをStringで指定し、エンコードの方式もStringで指定します。（デフォルトbinary）|
|@PartMap|Map<br>encoding(String)|マルチパートで送信するデータのキーをStringと値をMapで指定し、エンコードの方式もStringで指定します。（デフォルトbinary）|
|@Header|value(String)|HTTPヘッダーを設定できます。引数には設定するHeaderを指定します。|
|@HeaderMap|Map<String, String>|HTTPヘッダーを複数設定する時に使用します。キーには設定するHeader、値には設定するヘッダーの値を指定します。|


JSONを文字列として取得するには、 **RequestBody** や **ResponseBody** は[OKHttp](https://square.github.io/okhttp/)で提供されているクラスを利用します。詳細はこちらの[記事](https://qiita.com/naoi/items/8d493f00b0bbbf8a666c)を参照してください。

戻り値はCall型にして、実際にサーバから受け取るデータの型をジェネリクスで指定します。



#### Retrofitクラスのインスタンス化

Serviceの準備ができたらRetrofitクラスのインスタンスを取得します。

```kotlin:MainActivity.kt
// Retrofit本体
private val retrofit = Retrofit.Builder().apply {
    baseUrl("http://10.0.2.2:3000/")
}.build()
```

 **Retrofit.Builder** クラスでRetrofitクラスのインスタンスを生成します。
 **baseUrl** プロパティは必須となっています。baseUrlプロパティは **okhttp3.HttpUrl** でも大丈夫です。

最後にbuildメソッドを呼び出し、インスタンスを取得します。

#### Service（実装クラス）の取得

RetrofitクラスでService（実装クラス）を取得していきます。

```kotlin:MainActivity.kt
// サービスクラスの実装オブジェクト取得
private val service = retrofit.create(MyService::class.java)
```
Service（実装クラス）取得には、Javaのクラス情報を渡します。

### Callクラスの取得とHTTP通信
Service（実装クラス）から、実際にHTTP通信を行うCallクラスを取得します。
Service（インターフェイス）に定義したメソッドから行いたいHTTP通信にあったCallクラスを取得します。

```kotlin:MainActivity.kt
val get = service.getRawResponseForPosts()
```

HTTP通信を行うには、下記の２つのメソッドが用意されています。

|メソッド名|説明|
|:---|:---|
|execute()|同期処理であるため、ワーカスレッドで呼び出す必要がある。|
|enqueue(Callback<T> callback)|HTTP通信が終わったあとに実行したい処理をCallbackクラスで渡す必要あります。コールバック内はMainスレッドで実行されます。|

今回は同期処理のため、取得したCallクラスのexecuteメソッドを呼び出します。

```kotlin:MainActivity.kt
scope.launch {
    val responseBody = get.execute()
}
```

#### レスポンスから文字列の取得

実行結果はokhttp3.RequestBodyで受け取るため、bodyメソッドでレスポンスの内容を取得します。
サイトにstringメソッドでJSONの文字列を取得します。

```kotlin:MainActivity.kt
responseBody.body()?.let {

    myViewModel.result.postValue(it.string())

}
```

## POSTメソッドやPUTメソッドでJSON文字列を送信する実装方法
ここからのコードの解説は、GETメソッドとの差分のみを説明します。

### コード解説

#### 送信するデータを準備
今回はJSON文字列を生成するユーティリティクラスを作成しました。

```kotlin:Util.kt
package jp.co.casareal.retrofitbasic.util

object Util {

    fun createJson(id:String = "",title:String, author:String):String
            ="{" +
            "  \"id\": \"${id}\"," +
            "  \"title\": \"${title}\"," +
            "  \"author\": \"${author}\"" +
            "}"
}
```

実際に送信する文字列を生成します。

```kotlin:MainActivity.kt
val json = Util.createJson(
    title = myViewModel.title.value!!,
    author = myViewModel.author.value!!
)
```

#### RequestBodyのインスタンスを生成する

送信するデータはRequestBody送信するのでオブジェクトを生成します。
RequestBodyについては、こちらの[記事](https://qiita.com/naoi/items/8d493f00b0bbbf8a666c)を参照してください。
生成したRequestBodyをService（実装クラス）のメソッドの引数に渡し、送信データをCallクラスに格納します。

```kotlin:MainActivity.kt
val requestBody = RequestBody.create(mediaType, json)

val post = service.postRawRequestForPosts(requestBody)
```

# JSON文字列をオブジェクトに自動変換してWebAPIにHTTP通信する実装方法
先までのHTTP通信では送信するデータも文字列で用意し、レスポンスも文字列で受け取るため、データクラスへの変換は実装する必要があります。
しかし、Retrofitにはデータクラスへの変換機能が存在します。
ここからはデータクラスへの自動変換機能の実装方法を説明します。

今回は自動変換させるために、Gsonのconverterを利用します。
Gsonについては、この[記事](https://qiita.com/naoi/items/6b184700b2a41fb46356)を参照してください。

また、これまでの説明と重複するところは省略します。

## 実行イメージ

実行イメージは以下のようになります。
<img width="30%" alt="ファイル名" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99960/e07660b7-c355-e10d-ab9d-4b5972c28a26.gif">


![実行イメージ.gif]()

## サンプルコード
[GitHub](https://github.com/ktaro-nakaya/qiita-retrofit-with-converter)からダウンロードしてください。

## GETメソッドでHTTP通信する実装方法
さっそくGETメソッドのHTTP通信の実装方法を説明します。

### コード解説

#### build.gradle(app)への追記

はじめに、Retrofitのライブラリーをdependenciesに自動変換の機能であるconverterを追記します。

```groovy:build.gradle(app)
dependencies {
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
}

```

#### サーバとやり取りするデータクラスの定義
サーバと送受信するデータクラスを定義しておきます。

```kotlin:Post.kt
package jp.co.casareal.retrofitwithconverter.model

data class Post(

    val id: String = "",
    val title: String,
    val author: String
)
```


#### Serviceクラスの定義

Serviceクラスの定義もCallのジェネリクスや引数をデータクラスの方に変更します。

```kotlin:MyService.kt
package jp.co.casareal.retrofitwithconverter

import jp.co.casareal.retrofitwithconverter.model.Post
import okhttp3.RequestBody
import okhttp3.ResponseBody
import retrofit2.Call
import retrofit2.http.*

interface MyService {
    @GET("posts")
    fun getRawResponseForPosts(): Call<List<Post>>

    @POST("posts")
    fun postRawRequestForPosts(@Body post: Post): Call<Post>

    @PUT("posts/{id}")
    fun putRawRequestForPosts(@Path("id") id: String, @Body post: Post): Call<Post>

    @DELETE("posts/{id}")
    fun deletePathParam(@Path("id") id: String): Call<Post>

}
```

#### Retrofitクラスの取得
自動変換機能を追加するにはRetrofitクラスを取得するときに、自動変換してくれるconverterをしていします。

```kotlin:MainActivity.kt
// Retrofit本体
private val retrofit = Retrofit.Builder().apply {
    baseUrl("http://10.0.2.2:3000/")
    addConverterFactory(GsonConverterFactory.create())
}.build()
```

addConverterFactoryメソッドで自動変換するconverterを渡します。
今回は、GsonConverterFactory#createでConverterFactoryを取得して渡しています。


#### HTTP通信で取得できる型
converterを指定することにより、JSON文字列→データクラスへ自動で変換されます。

そのため、Call#executeメソッドの戻り値でbodyメソッドを呼び出すと、Service（インターフェイス）で指定したデータクラスの型に変換されます。

```kotlin:MainActivity.kt
val list = response.body()
```

## POSTメソッドやPUTメソッドでJSON文字列を送信する実装方法
ここからのコードの解説は、GETメソッドとの差分のみを説明します。

### コード解説
基本的に差分としては、引数に送信するデータをデータクラス型にしておいても、送信時にJSONへ自動変換されます。

#### 送信するデータを準備
データクラスは定義済みであるため、データクラスのインスタンスに送信するデータを設定します。

```kotlin:MainActivity.kt
val data = Post(title = myViewModel.title.value!!, author = myViewModel.author.value!!)
```

#### Service(実装クラス）のメソッドにデータクラスのオブジェクトを渡す
JSONに自動変換するConverterを設定しているので、データクラスをJSON文字列に自動変換してくれます。
そのため、ここではデータクラスのオブジェクトを渡します。

```kotlin:MainActivity.kt
val post = service.postRawRequestForPosts(data)
```

その他はこれまで説明した通りの内容です。


# HTTPを非同期通信で実行する
これまで通信処理は同期処理をおこなうメソッドで実行していました。しかし、昨今のAndroidでは通信を非同期処理で行います。
もちろんRetrofitも非同期処理に対応したメソッドが用意されています。
移行は非同期処理の実装方法について説明します。

また、これまでの説明と重複するところは省略します。

## 実行イメージ

実行イメージは以下のようになります。
<img width="30%" alt="ファイル名" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99960/2be92d46-6abb-2212-2d91-3819c9989f55.gif">

## サンプルコード
[GitHub](https://github.com/ktaro-nakaya/qiita-retrofit-with-async)からダウンロードしてください。

## 非同期処理でHTTP通信を行う実装方法
HTTPメソッドに関わらず、非同期処理の実装方法は共通です。

### コード解説

#### 非同期で通信を行う
非同期処理を行うには先に説明したとおり、Call#enqueueを呼び出します。
引数にはCallbackクラスのオブジェクトを渡します。
CallBackクラスは戻り値の型をジェネリクスで指定します。

```kotlin:MainActivity.kt
val get = service.getRawResponseForPosts().also {

            it.enqueue(object : Callback<List<Post>> {
                override fun onFailure(call: Call<List<Post>>, t: Throwable) {
                    Toast.makeText(this@MainActivity, "通信エラー", Toast.LENGTH_SHORT).show()
                    Log.e("TEST", "通信エラー", t)
                }

                override fun onResponse(call: Call<List<Post>>, response: Response<List<Post>>) {
                    myViewModel.result.value = response.body().toString()
                }
            })
        }
}
```

CallBackクラスには２つのメソッドがあり。
HTTP通信の成否によって実装内容を記述できます。

|メソッド名|引数|説明|
|:---|:---|:----|
| onFailure|Call<T> call<br>Throwable t|Callは実行したHTTP通信のCallのオブジェクト。Throwableは通信に失敗したときのエラーの内容が格納されています。|
| onResponse|Call<T> call<br> Response<T> response|Callは実行したHTTP通信のCallのオブジェクト。Responseはexecuteのときと同じResponseオブジェクトです。|

# まとめ
Retrofitを使ってきましたが、ある程度OKHttpなどを理解していないと難しいところもありますが、非同期処理に対応したメソッドがあり便利だと感じました。