Kotlin + Retrofit でAPIコールしてみた

#はじめに

二年目になりました。
kotlinでAndroid開発を勉強中です。
とりあえずAPIでとってきたデータを表示するところまでできたので、メモ書き程度に残します。


#やりたいこと
楽天ウェブサービスの楽天商品ランキングAPIで総合ランキングをとってきて、リストに表示する！
（本当は順位や画像、価格など表示したいのですが、一旦商品名だけ出します）
![index.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308235/e2fec639-f9a8-04d2-0d42-6b29f3b48670.gif)


#環境と使うもの

####環境
- Mac OS Mojave v10.14.5
- Android Studio 3.4.1


####使う物
- 楽天ウェブサービス(https://webservice.rakuten.co.jp/)
- Kotlin 1.3.31
- Retrofit 2.4.0


#やってみよう
1.環境を整える
2.表示する場所をつくる
3.APIからの返り値を置く場所を作る
4.APIを呼ぶ

##環境を整える

- 楽天ウェブサービスに登録する

楽天ウェブサービスに登録し、APIをコールする際に必要になる`applicationId`を取得します。
取得の仕方はこちらを参考にさせていただきました。
→[**楽天（Rakuten Developers）のアプリIDとアフィリエイトIDを取得する方法**](https://wp-ap.net/help/rakuten-appli-id-affili-id/)


- retrofitを使えるように`build.gradle`に以下を追加する

```build.gradle
def retrofitVersion = '2.4.0'
    implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
    implementation "com.squareup.retrofit2:adapter-rxjava:$retrofitVersion"
    implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion"
```

- HTTP通信ができるように、`AndroidManifest.xml`に以下を追加する

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

manifestタグ内に入れましょう。
これがないと**HTTP通信ができません！**


- Activityを作成する

プロジェクトビューで左クリックし、
新規 ＞ アクティビティー ＞ 空のアクティビティー
と選択します。
![スクリーンショット 2019-06-27 16.19.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308235/f6d9dcf4-9815-137b-a1e9-cdee3aa88a60.png)

アクティビティー名を入力し、`レイアウトファイルを生成する`にチェックをいれます。
レイアウト名が自動で入ります。（好きなように変更できます）
パッケージ名はご自身の環境にあるものをお使いください。
完了を押すと、以下の２つのファイルが生成されます。
　- RakutenActivity.kt
　- activity_rakuten.xml

![スクリーンショット 2019-06-27 16.25.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308235/ad264810-4615-45cd-41ef-0ada2dc0b7ce.png)


##表示する場所を作る

先程作成した`activity_rakuten.xml`で、レイアウトを作成していきます。
GUIで作成することが可能ですが、今回は完成しているものをxmlで載せておきます。

```activity_rakuten.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".RakutenActivity">

    <Button
            android:text="ランキングを表示する"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/button"
            app:layout_constraintStart_toStartOf="parent"
            android:layout_marginLeft="8dp" android:layout_marginStart="8dp" app:layout_constraintEnd_toEndOf="parent"
            android:layout_marginEnd="8dp" android:layout_marginRight="8dp" app:layout_constraintHorizontal_bias="0.498"
            android:onClick="getRanking"
            android:layout_marginTop="16dp" app:layout_constraintTop_toTopOf="parent"/>
    <TextView
            android:layout_width="0dp"
            android:layout_height="36dp"
            android:id="@+id/titleRanking"
            android:textSize="18sp" android:gravity="center_horizontal|center_vertical" android:layout_marginStart="8dp"
            app:layout_constraintStart_toStartOf="parent" android:layout_marginLeft="8dp" android:layout_marginEnd="8dp"
            app:layout_constraintEnd_toEndOf="parent" android:layout_marginRight="8dp"
            app:layout_constraintHorizontal_bias="0.0" android:layout_marginTop="8dp"
            app:layout_constraintTop_toBottomOf="@+id/button"/>
    <ListView
            android:layout_width="395dp"
            android:layout_height="618dp"
            android:id="@+id/listRanking" app:layout_constraintEnd_toEndOf="parent"
            android:layout_marginEnd="8dp" android:layout_marginRight="8dp" android:layout_marginTop="8dp"
            app:layout_constraintTop_toBottomOf="@+id/titleRanking"/>

</androidx.constraintlayout.widget.ConstraintLayout>
```

`Button`を押すと、`getRanking`イベントが起こり、
`TextView` タグ内にランキングのタイトルを、
`ListView` タグ内にランキング上位から商品の名前を詰めていきます。

##APIからの返り値を置く場所を作る

データクラスなるものを作ります。
JsonToKotlinClassというプラグインを使用しました。
以下の記事の通りに行いました。
[**kotlinでJSON扱うならJsonToKotlinClassが便利**](https://qiita.com/thr3a/items/612ebbfb85e21c8b8f15)

記事のとおりに行うと以下のようなクラスが出来上がります。

```RakutenRankingResult.kt
data class RakutenRankingResult(
    val Items: List<Item>,
    val lastBuildDate: String,
    val title: String
)

data class Item(
    val affiliateRate: String,
    val affiliateUrl: String,
    val asurakuArea: String,
    val asurakuClosingTime: String,
    val asurakuFlag: Int,
    val availability: Int,
    val carrier: Int,
    val catchcopy: String,
    val creditCardFlag: Int,
    val endTime: String,
    val genreId: String,
    val imageFlag: Int,
    val itemCaption: String,
    val itemCode: String,
    val itemName: String,
    val itemPrice: String,
    val itemUrl: String,
    val mediumImageUrls: List<String>,
    val pointRate: Int,
    val pointRateEndTime: String,
    val pointRateStartTime: String,
    val postageFlag: Int,
    val rank: Int,
    val reviewAverage: String,
    val reviewCount: Int,
    val shipOverseasArea: String,
    val shipOverseasFlag: Int,
    val shopCode: String,
    val shopName: String,
    val shopOfTheYearFlag: Int,
    val shopUrl: String,
    val smallImageUrls: List<String>,
    val startTime: String,
    val taxFlag: Int
)
```

##APIを呼ぶ

前半で作成した`RakutenActivity.kt`に書き足していきます。

###interface
APIを表現、定義します。

`@`の後に基本のURLに続くURLを書きます。
本来であればクエリパラメータとして渡すのが正しい方法のような気はしますが、本記事ではできるだけシンプルに記述したいので、べた書きしてます。
`applicationId`は、最初に取得したご自身のIDを書いてください。`[]`は不要です

```RakutenActivity.kt
private val itemInterface by lazy { createService() }

    interface ItemInterface {
        @GET("IchibaItem/Ranking/20170628?formatVersion=2&applicationId=[applicationId]")
        fun items(): retrofit2.Call<RakutenRankingResult>
}
```





###HttpClient
基底URLの定義などを行います。

`baseApiUrl`が基本のURLです。
`/`まで入れるのが基本?と見たような気がします。ソースは不明です。申し訳ありません。

```RakutenActivity.kt
fun createService(): ItemInterface {
    val baseApiUrl = "https://app.rakuten.co.jp/services/api/"

    val httpLogging = HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
    val httpClientBuilder = OkHttpClient.Builder().addInterceptor(httpLogging)

    val retrofit = Retrofit.Builder()
        .addConverterFactory(GsonConverterFactory.create())
        .baseUrl(baseApiUrl)
        .client(httpClientBuilder.build())
        .build()

    return retrofit.create(ItemInterface::class.java)
}
```

###Retrofit instance
このserviceは、APIを定義したinterfaceをインスタンス化したものであり、実際にAPIコールを行う際に利用されます。

メソッド名`getRanking`は、レイアウトで作成したボタンの`onClick`属性に入力します。

`onFailure`がAPIコール失敗時に呼ばれる処理、`onResponse`が成功時に呼ばれる処理です。
`onFailure`には何も書いていません。エラーハンドリングしたい方はここに書きましょう。

`title`にランキングのタイトルをつめて、`titleRanking`というidのtextViewで表示しています。

for文でレスポンスのItemsを回し、`itemName`を`items`につめていきます。
ArrayAdapterを使用し、`items`につめた`itemName`を、`listRanking`に順番に表示します。

```RakutenActivity.kt
fun getRanking(v: View){
    itemInterface.items().enqueue(object : retrofit2.Callback<RakutenRankingResult> {
        override fun onFailure(call: retrofit2.Call<RakutenRankingResult>?, t: Throwable?) {
        }

        override fun onResponse(call: retrofit2.Call<RakutenRankingResult>?, response: retrofit2.Response<RakutenRankingResult>) {
            if (response.isSuccessful) {
                response.body()?.let {

                    var items = mutableListOf<String>()
                    var res = response.body()?.Items?.iterator()

                    var title = response.body()!!.title
                    titleRanking.text = "$title"

                    if (res != null) {
                        for (item in res) {
                            items.add(item.itemName)
                        }
                    }

                    val adapter = ArrayAdapter(this@RakutenActivity, android.R.layout.simple_list_item_1, items)
                    val list: ListView = findViewById(R.id.listRanking)
                    list.adapter = adapter
                }
            }
        }
    })
}

```
以上で完成です。ランキングは出てきましたか？

#さいごに
はじめてandroid開発を行っています。
java経験もほぼ皆無なので苦戦中です。
なにか間違っていることや、もっとスマートな書き方等あればお教えいただきたいです。

#参考
- [Android Retrofit2 with Kotlin](https://qiita.com/SYABU555/items/3b280a8e81d2cc897383)
- [kotlin android (retrofit2 + gson + list_view)](https://qiita.com/okb_m/items/212c6f4f0476e9a5d150)