Help us understand the problem. What is going on with this article?

Android + Kotlin 1.3のcoroutines(Async, Await)でHTTP通信を非同期処理

はじめに

Kotlin 1.3がリリースされ、coroutinesが正式版になりました。
Kotlin 1.3にバージョンを上げるとKotlin 1.2系で動いていたcoroutinesの処理が動かなくなってしまったため、少しコードの修正が必要になりました。

過去に投稿した
AndroidでKotlinのcoroutine(Async, Await)を使ってサクッとHTTP通信(非同期処理)を行う
をKotlin 1.3でも動くように修正しました。
(前回の記事と内容はそこまで変わらないです。このスニペットの背景などは上記ページを参照ください)

2019年2月16日更新

ライブラリのバージョンを更新しました。
サンプルコードを公開しました。
https://github.com/jonghyo/android-http-async-await

2019年3月27日更新

Okhttp3のバージョンが3.13以降の場合
Javaの compileOptionsでJava 8機能を有効にしないとエラーが発生するので修正しました。
@kk2170さん、ありがとうございます!

2019年4月18日更新

Kotlin1.3.30がリリースされたので、Kotlinのバージョンを修正しました。

2019年6月2日更新

GlobalScope.launch(Dispatchers.Main)をむやみに利用することはアンチパターンとされています。Kotlin Coroutineを使う際のスコープに関して触れたセクションを追加しました。
@tetsu0831 さん、ありがとうござます!

2019年7月14日更新

Kotlin1.3.40がリリースされたので、Kotlinのバージョンを修正しました。
また、kotlinx-coroutinesのバージョンを上げました。

2019年8月24日更新

Kotlin1.3.50がリリースされたので、Kotlinのバージョンを修正しました。
また、kotlinx-coroutines,OKHttp3のバージョンを上げました。

Kotlin 1.3.50の内容をまとめたのでもしよろしければ読んでください!
Kotlin 1.3.50がリリースされました!!

事前準備

Gradle設定

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

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

Okhttp3のバージョンが3.13以降の場合、 compileOptionsでJava 8の機能を有効化する必要があります。

詳細は、 @kk2170 さんの記事にて解説されています。
ありがとうございます!

OkHttp3をAndroidで使おうとしたときにjava.lang.ClassCastException: Bootstrap methaod returned nullが発生して初期化に失敗する

app/build.gradle
android {
//compileOptions以前の記述は省略
    compileOptions {
       sourceCompatibility JavaVersion.VERSION_1_8
       targetCompatibility JavaVersion.VERSION_1_8
    }
}

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

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

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

HTTP通信処理を書く

今回はHTTP GETを行うメソッドを定義しました。
関数の呼び出し元でasyncなどを使うことが推奨されているようなので少し修正しました。

HttpUtil.kt
class HttpUtil {
    //叩きたいREST APIのURLを引数とします
        fun httpGET1(url : String): String? {
        val client = OkHttpClient()
        val request = Request.Builder()
                .url(url)
                .build()

        val response = client.newCall(request).execute()
        val body = response.body?.string()
        return body
    }
}

OkHttp 4.0.0以降はresponse.bodyの書き方が変わったみたいです。
Getting an error Using 'body(): ResponseBody?' is an error. moved to val with okhttp

ライブラリのimport文の修正

coroutines関連がKotlin1.3よりexperimentalではなくなったので修正します。

MainActivity.kt(修正前)
import kotlin.coroutines.experimental,Dispatchers
import kkotlin.coroutines.experimental.GlobalScope
import kotlin.coroutines.experimental.async
import kotlin.coroutines.experimental.launch
MainActivity.kt(修正後)
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.launch

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) {
        val http = HttpUtil()
    //Mainスレッドでネットワーク関連処理を実行するとエラーになるためBackgroundで実行
        async(Dispatchers.Default) { http.httpGET1(URL) }.await().let {
    //minimal-jsonを使って jsonをパース
            val result = Json.parse(it).asObject()
            val textView = findViewById(R.id.text) as TextView
            textView.setText(result.get("description").asObject().get("text").asString())
        }
    }

Kotlin Coroutineスコープについて(2019年6月2日追記)

KotlinでCoroutineを利用する場合、Coroutineのスコープについてケアする必要があります。
GlobalScope.launch(Dispatchers.Main)のようにGlobalScopeを目的もなく使うのはKotlin Coroutinesのアンチパターンとされています。

Kotlin Coroutines patterns & anti-patterns

上記を和訳、解説した @ikemura23さんの記事
Kotlin Coroutinesパターン&アンチパターン

GlobalScopeはアプリケーションの有効期間全体にわたって動作するため
GlobalScopeを同一アプリ内で乱用すると不都合が発生したり
Androidのアプリケーションライフサイクルとの間で問題が発生する可能性もあります。

単に、REST APIを叩く検証するだけであればrunBlokingなどの利用も検討すると良いでしょう。

【Kotlin】Coroutineを理解する

また、プロダクションコードではアプリケーション定義の
CoroutineScopeを利用することがベターだとされています。

図で理解する Kotlin Coroutine

@tetsu0831 さん、ご指摘ありがとうござます!

結果

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

所感

Kotlin1.2系のcoroutines処理のままでは、Kotlin1.3系では動きませんでしたが
そこまで大幅な修正は必要なさそうでした。
Kotlin1.3系のドキュメントがもう少し充実してくると嬉しいな。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away