【Android】Retrofit2を使う【Kotlin】

KotlinでRetrofit2を使ってみました(2018/9/8全面的に書き直し)。

Retrofitとは

httpで公開されたAPI叩くライブラリです。
Pairsの中の人に聞いた所、事情がなければAndroidアプリ作ってる会社は基本的にRetrofitを使ってるそうです。

プロジェクト

プロジェクトはGitHubに上げてあります。
github.com

やったこと

  • Gradleに追記
  • Manifestに追記
  • 受け取るためのデータクラス作成
  • Retrofitを使う
Gradleに追記

Module.appに以下2行を追加します。

implementation 'com.squareup.retrofit2:retrofit:2.4.0'
implementation 'com.squareup.retrofit2:converter-gson:2.4.0'

追記後全文。

apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

android {
    compileSdkVersion 27
    defaultConfig {
        applicationId "com.wrongwrong.retrofittest"
        minSdkVersion 15
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    //for Retrofit2
    implementation 'com.squareup.retrofit2:retrofit:2.4.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.4.0'

    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation 'com.android.support.constraint:constraint-layout:1.1.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
Manifestに追記

ネットワークを使うのでパーミッションを書きます。

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

追記後全文。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.wrongwrong.retrofittest">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
受け取るためのデータクラス作成

今回はGitHubAPIのreposを取得します。取得するデータ一覧はこちら
今回はデータの変数名をAPIデータと合わせていますが、実際はキャメルケースで書いてもちゃんと動きます。

package com.wrongwrong.retrofittest

import java.io.Serializable

data class Owner(
        var login: String,
        var id: Long,
        var node_id: String,
        var avatar_url: String,
        var gravatar_id: String,
        var url: String,
        var html_url: String,
        var followers_url: String,
        var following_url: String,
        var gists_url: String,
        var starred_url: String,
        var subscriptions_url: String,
        var organizations_url: String,
        var repos_url: String,
        var events_url: String,
        var received_events_url: String,
        var type: String,
        var site_admin: Boolean
): Serializable

data class Permissions(
        var admin: Boolean,
        var push: Boolean,
        var pull: Boolean
): Serializable

data class License(
        var key: String,
        var name: String,
        var spdx_id: String,
        var url: String,
        var name_id: String
): Serializable

data class Repo(
        var id: Long,
        var node_id: String,
        var name: String,
        var full_name: String,

        var owner: Owner,

        var private: Boolean,
        var html_url: String,
        var description: String?,
        var fork: Boolean,
        var url: String,
        var archive_url: String,
        var assignees_url: String,
        var blobs_url: String,
        var branches_url: String,
        var collaborators_url: String,
        var comments_url: String,
        var commits_url: String,
        var compare_url: String,
        var contents_url: String,
        var contributors_url: String,
        var deployments_url: String,
        var downloads_url: String,
        var events_url: String,
        var forks_url: String,
        var git_commits_url: String,
        var git_refs_url: String,
        var git_tags_url: String,
        var git_url: String,
        var issue_comment_url: String,
        var issue_events_url: String,
        var issues_url: String,
        var keys_url: String,
        var labels_url: String,
        var languages_url: String,
        var merges_url: String,
        var milestones_url: String,
        var notifications_url: String,
        var pulls_url: String,
        var releases_url: String,
        var ssh_url: String,
        var stargazers_url: String,
        var statuses_url: String,
        var subscribers_url: String,
        var subscription_url: String,
        var tags_url: String,
        var teams_url: String,
        var trees_url: String,
        var clone_url: String,
        var mirror_url: String,
        var hooks_url: String,
        var svn_url: String,
        var homepage: String,
        var language: String?,
        var forks_count: Int,
        var stargazers_count: Int,
        var watchers_count: Int,
        var size: Int,
        var default_branch: String,
        var open_issues_count: Int,
        var topics: List<String>,
        var has_issues: Boolean,
        var has_projects: Boolean,
        var has_wiki: Boolean,
        var has_pages: Boolean,
        var has_downloads: Boolean,
        var archived: Boolean,
        var pushed_at: String,
        var created_at: String,
        var updated_at: String,
        var permissions: Permissions,
        var allow_rebase_merge: Boolean,
        var allow_squash_merge: Boolean,
        var allow_merge_commit: Boolean,
        var subscribers_count: Int,
        var network_count: Int,
        var license: License
): Serializable
Retrofitを使う

今回はonCreate内でhttps://api.github.com/users/{id}/reposにアクセスしてデータを取得し、0番目のリポジトリのフルネームをLogに出力します。
インターフェース内には、GET, POST, PUT, DELETEなど、Rest APIにおけるCRUD処理が全て書けます。今回はデータを取得していますが、POST処理などでデータが返ってこない場合は戻り値をCall<Unit>*1とします。
retrofitとserviceはクラス内変数として宣言していますが、これらの初期化は処理コストが重いため、実際はグローバルに用意して使うのが良いようです。少なくともそのまま関数に書くのは悪い使い方です。

package com.wrongwrong.retrofittest

import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import com.google.gson.GsonBuilder
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.GET
import retrofit2.http.Path
import java.io.IOException

class MainActivity : AppCompatActivity() {
    interface IGetRepos{
        @GET("{id}/repos")
        fun getRepos(@Path("id") userID : String) : Call<List<Repo>>
    }

    private val retrofit: Retrofit = Retrofit.Builder()
            .addConverterFactory(GsonConverterFactory.create(GsonBuilder().serializeNulls().create()))
            .baseUrl("https://api.github.com/users/")
            .build()
    private val service: IGetRepos = retrofit.create(IGetRepos::class.java)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val call = service.getRepos("k163377")
        call.enqueue(object : Callback<List<Repo>> {
            override fun onResponse(call: Call<List<Repo>>?, response: Response<List<Repo>>?) {
                try{
                    var arr: List<Repo>? = response!!.body()
                    Log.d("onResponse", arr!![0].full_name)
                }catch (e: IOException){
                    Log.d("onResponse", "IOException")
                }
            }

            override fun onFailure(call: Call<List<Repo>>?, t: Throwable?) {
                TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
            }
        })
    }
}

参考URL

mainActivityの大部分はこちらから写経させて頂きました。 Retrofit2Demo/Example01.java at master · ikidou/Retrofit2Demo · GitHub
もっと詳しい解説です。 Android Retrofit2 with Kotlin - Qiita
AndroidでRxJavaと組み合わせた例です。 KotlinでRetrofitとRxを使ってAPIクライアントをサクッと実装する - Qiita

*1:UnitはJavaでいうvoidです