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

# はじめに
~~[Kotlin 1.3](https://kotlinlang.org/docs/reference/whatsnew13.html)がリリースされ、coroutinesが正式版になりました。~~
~~Kotlin 1.3にバージョンを上げるとKotlin 1.2系で動いていたcoroutinesの処理が動かなくなってしまったため、少しコードの修正が必要になりました。~~

2020年8月17日に[Kotlin 1.4](https://blog.jetbrains.com/ja/kotlin/2020/08/kotlin-1-4-released-with-a-focus-on-quality-and-performance-ja/)がリリースされました:tada:

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

**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がリリース](https://blog.jetbrains.com/kotlin/2019/04/kotlin-1-3-30-released/)されたので、Kotlinのバージョンを修正しました。

**2019年6月2日更新**

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

**2019年7月14日更新**

[Kotlin1.3.40がリリース](https://blog.jetbrains.com/kotlin/2019/06/kotlin-1-3-40-released/)されたので、Kotlinのバージョンを修正しました。
また、kotlinx-coroutinesのバージョンを上げました。

**2019年8月24日更新**

[Kotlin1.3.50がリリース](https://blog.jetbrains.com/kotlin/2019/08/kotlin-1-3-50-released/)されたので、Kotlinのバージョンを修正しました。
また、kotlinx-coroutines,OKHttp3のバージョンを上げました。

Kotlin 1.3.50の内容をまとめたのでもしよろしければ読んでください!
[Kotlin 1.3.50がリリースされました!!](https://qiita.com/jonghyo/items/3c9e6b0624aa7743e563)

**2020年3月7日更新**
[Kotlin1.3.70がリリース](https://blog.jetbrains.com/kotlin/2020/03/kotlin-1-3-70-released/)されたので、Kotlinのバージョンを修正しました。
また、kotlinx-coroutines,OKHttp3のバージョンを上げました。

**2020年8月7日更新**
livedoor Weather APIが[2020年7月31日をもって提供終了となったため](http://tanweb.net/2020/08/02/34336/)本記事のLGTM!を表示するサンプルに変更しました。GitHubのサンプルコードも修正しました。

@yuki-kamikita さん、ご指摘ありがとうございます!

**2020年8月17日更新**
[Kotlin1.4がリリース](https://blog.jetbrains.com/ja/kotlin/2020/08/kotlin-1-4-released-with-a-focus-on-quality-and-performance-ja/)されたので、Kotlinのバージョンを修正しました。
また、kotlinx-coroutinesのバージョンを上げました。


# 事前準備
### Gradle設定
使用するライブラリの依存解決のため、以下の記述をappのbuild.gradleに追加します。
Kotlin coroutine用ライブラリは2020年8月17日時点で最新の1.3.9を利用します。

```gradle:app/build.gradle
dependencies {
    implementation 'com.squareup.okhttp3:okhttp:4.4.0'  //http通信ライブラリ
    implementation 'com.eclipsesource.minimal-json:minimal-json:0.9.5' //jsonパースライブラリ
    def coroutines_version = '1.3.9' //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が発生して初期化に失敗する](https://qiita.com/kk2170/items/472dddb86b373f52f8e9)

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

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

```gradle:build.gradle
ext.kotlin_version = '1.4.0'
```

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

```kotlin:HttpUtil.kt
object HttpClient {
    //OKHttp3はシングルトンで使う
    val instance = OkHttpClient()
}
```
参考
[KotlinでRetrofit2/OkHttp3を使ってXMLを取得する](https://qiita.com/rei-m/items/ff49e3fddfd8b6f5311c)

```kotlin:HttpUtil.kt
class HttpUtil {

    fun httpGet(url : String): String? {
        val request = Request.Builder()
                .url(url)
                .build()

        val response = HttpClient.instance.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
](https://stackoverflow.com/questions/56913085/getting-an-error-using-body-responsebody-is-an-error-moved-to-val-with-ok)

# ライブラリのimport文の修正
coroutines関連がKotlin1.3よりexperimentalではなくなったので修正します。
※こちらはKotlin 1.2系から1.3系以降にバージョンアップする場合に必要です。

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

```kotlin:MainActivity.kt(修正後)
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
```

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

```kotlin:MainActivity.kt
class MainActivity : AppCompatActivity() {
    val URL = "https://qiita.com/api/v2/items/bf3e4e06022eebe8e3eb" //サンプルとしてQiitaのAPIサービスを利用します
    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.httpGet(URL) }.await().let {
    //minimal-jsonを使って　jsonをパース
            val result = Json.parse(it).asObject()
            val textView = findViewById(R.id.text) as TextView
            textView.setText(result.get("likes_count").asInt().toString() + "LGTM!")
        }
    }
```

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

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

[Kotlin Coroutines patterns & anti-patterns](https://proandroiddev.com/kotlin-coroutines-patterns-anti-patterns-f9d12984c68e)

上記を和訳、解説した @ikemura23さんの記事 
[Kotlin Coroutinesパターン＆アンチパターン](https://qiita.com/ikemura23/items/fb8caeba4c35fcd85644) 

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

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

[【Kotlin】Coroutineを理解する](https://qiita.com/AtsushiUemura/items/fa3e84a9f6eacd509205)

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

[図で理解する Kotlin Coroutine](https://qiita.com/kawmra/items/d024f9ab32ffe0604d39)

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

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

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254318/546240a1-6302-b1d1-2c49-880956254882.gif" width=50%>

# 所感
~~Kotlin1.2系のcoroutines処理のままでは、Kotlin1.3系では動きませんでしたがそこまで大幅な修正は必要なさそうでした。~~

Kotlin 1.3系で正式採用されたcoroutinesもこなれてきた感がありますね。
Kotlin 1.4では[Coroutine Debugger](https://kotlinlang.org/docs/reference/whatsnew14.html?_ga=2.241419403.822575477.1597854136-693399959.1585876459#coroutine-debugger)も実装されより便利になったみたいです!
Kotlin 1.4についての記事も書きたいな。

---
最後までお読み頂きありがとうございました!
Twitterでも技術ネタやデザインについてなどもツイートしているので、よかったらフォローしてもらえると嬉しいです:stuck_out_tongue_closed_eyes:
→　[Twitter@jonghyo_](https://twitter.com/jonghyo_)