AndroidでKotlinのcoroutine(Async, Await)を使ってサクッとHTTP通信(非同期処理)を行う


はじめに

Kotlinを用いてAndroidアプリ開発を行っていて
「REST APIを叩いて取得した結果をもとにUIを更新する」際に

  • HTTP通信はメインスレッド外で行う必要がある(非同期処理が必要)
  • 取得したjsonデータのパースが面倒(Gson, moshiなどいろいろとライブラリはあるが、jsonのデータスキームを定義するのが面倒。)
  • UI更新はメインスレッドで行う必要がある(AsyncTaskなどで非同期処理を行うと処理が若干長くなる)

などの制約があるため、個人的に上記を楽に行える汎用スニペットを作成しました。

今回作成したスニペットのポイントは

  • HTTP通信には、OKHttp3を使用
  • jsonのパースに、データモデルの定義不要なminimal-jsonを使用
  • 非同期処理に、Async, Await (kotlin.coroutins) を使用

2018/12/2更新

Kotlin1.3がリリースされ、coroutinesが正式版になりました。
Kotlin1.3にバージョンを上げると少しコードの修正が必要になります。

Kotlin1.3に対応した修正版はごちゃっとするので別ページにまとめました。
Android + Kotlin1.3のcoroutinesで非同期HTTP通信

Kotlin1.2系をわざわざ使う理由のない方は上記を参照いただく方が良いかと思います!

事前準備

Gradle設定

使用するライブラリの依存解決のため、以下の記述をappのbuild.gradleに追加します。
Kotlin coroutine用ライブラリは2018年9月21日時点で最新の0.26.1を利用します。

kotlin-coroutines 0.26.1では破壊的な更新がいろいろと行われているようです。

参考
kotlin-coroutines 0.26.1での破壊的更新

app/build.gradle
dependencies {
    implementation 'com.squareup.okhttp3:okhttp:3.11.0'  //http通信ライブラリ
    implementation 'com.eclipsesource.minimal-json:minimal-json:0.9.5' //jsonパースライブラリ
    def coroutines_version = '0.26.1' //kotlinコルーチン用ライブラリ(async, await)のバージョン
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" //kotlinコルーチン用ライブラリ(async, await)
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" //kotlinコルーチン用ライブラリ(async, await)
}

使用するKotlinのバージョン変更

プロジェクトのbuild.gradleで、Kotlin coroutineを使うためにKotlinのバージョンを変更します。

build.gradle
ext.kotlin_version = '1.2.61' //Kotlin coroutineを使う際にバージョンを変更する必要あり

Kotlin coroutineを使う旨をgradle.propertiesに記述します。

gradle.properties
//Kotlin coroutineを使うのに必要
kotlin.coroutines=enable

非同期処理に対応したHTTP通信処理を書く

今回はHTTP GETを行うメソッドを定義しました。

HttpUtil.kt
class HttpUtil {
    //叩きたいREST APIのURLを引数とします
    fun httpGET(url : String): Deferred<String?> = GlobalScope.async(Dispatchers.Default, CoroutineStart.DEFAULT, null, {
        val client = OkHttpClient()
        val request = Request.Builder() 
                .url(url)
                .build()

        val response = client.newCall(request).execute()
        return@async response.body()?.string() //asyncを使って非同期処理にします
    })
}

HTTP GETの結果を用いてUIを更新する

今回はボタンをクリックすると、HTTP GETの処理が走り、結果をTextViewに反映する処理を書きました。

MainActivity.kt
class MainActivity : AppCompatActivity() {
    val URL = "http://weather.livedoor.com/forecast/webservice/json/v1?city=400040" //サンプルとしてライブドアのお天気Webサービスを利用します
    var result = ""

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

        val getButton = findViewById(R.id.button) as Button
        getButton.setOnClickListener(object : View.OnClickListener {
            override
            fun onClick(view: View) {
                onParallelGetButtonClick()
            }
        })
    }

    //非同期処理でHTTP GETを実行します。
    fun onParallelGetButtonClick() = GlobalScope.launch(Dispatchers.Main, CoroutineStart.DEFAULT, null, {
        val http = HttpUtil()
        val res = http.httpGET(URL).await()
        val result = Json.parse(res).asObject()

        val textView = findViewById(R.id.text) as TextView
        textView.setText(result.get("description").asObject().get("text").asString())
    })
}

結果

HTTP GETを非同期処理で実行し、取得したjsonをTextViewに反映できました。

所感

非常に簡単に非同期処理でHTTP通信が実現できました。
minimal-jsonはhas系のメソッドがなかったりと、イケてない部分もありますがサクッと動かしたいときには今回のライブラリ群の組み合わせはいい感じかなと思います。