Androidのライフサイクルの基礎からViewModel, LiveData, Kotlin Coroutinesまでを流れるように説明したい

先日、Google Developer Expert for Androidになりました。 :tada: これからもよろしくおねがいいたします。
Androidの初心者がステップアップできるような記事を書いてみます。
なにかツッコミがあればコメントしてください🙏

ライフサイクルについて少し学んだ後に、ViewModel, LiveData, Kotlin Coroutinesについて、ライフサイクルに関連する課題とその解決策という位置づけで話していきます。

# 1. ライフサイクルの基礎

ライフサイクルとは生き物の蝶でいうと生まれて、卵から幼虫になってさなぎになって、蝶になって死んでいく感じですが、AndroidのActivityやFragmentという表示を持つコンポーネントにもライフサイクルがあります。
どのようなライフサイクルの状態があるのか、どのようなライフサイクルのメソッドがあるのかを説明していきます。
なんとなくわかる方は2.の画面回転からどうぞ。

## Activityのライフサイクルの状態

Actiivtyのライフサイクルの変化で呼ばれるメソッドがあるので、それぞれのメソッドにログを入れて動作を確認してみましょう。
最近使われるActivity(AppCompatActivity)には今のライフサイクルの状態を確認できる`getLifecycle()`というメソッドが追加されており、それにより、ライフサイクルの状態を確認できます。

```kotlin
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Log.d("MainActivity", "onCreate state:" + lifecycle.currentState)
    }

    override fun onStart() {
        super.onStart()
        Log.d("MainActivity", "onStart state:" + lifecycle.currentState)
    }

    override fun onResume() {
        super.onResume()
        Log.d("MainActivity", "onResume state:" + lifecycle.currentState)
    }

    override fun onPostResume() { // onPostResume()はonResumeの後に呼ばれます。
        super.onPostResume()
        Log.d("MainActivity", "onPostResume state:" + lifecycle.currentState)
    }

    override fun onPause() {
        super.onPause()
        Log.d("MainActivity", "onPause state:"+lifecycle.currentState)
    }

    override fun onStop() {
        super.onStop()
        Log.d("MainActivity", "onStop state:"+lifecycle.currentState)
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d("MainActivity", "onDestroy state:"+lifecycle.currentState)
    }
}
```

アプリを起動すると以下のようなログが出ます。

``` 
onCreate state:INITIALIZED
onStart state:CREATED
onResume state:STARTED
onPostResume state:RESUMED
```

まずそれぞれの状態を説明しましょう。Activityには以下のような状態があります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/965fa5bb-52c8-319f-1de7-0cf648abc043.png" width=300 />
(codelabより https://codelabs.developers.google.com/codelabs/kotlin-android-training-lifecycles-logging/index.html?index=..%2F..android-kotlin-fundamentals#2 )

まずActivityがインスタンス化されてすぐのデフォルトの状態では`state:INITIALIZED`状態であり、その後`onCreate`の後に`state:CREATED`になり、`onStart`の後に`state:STARTED`、`onResume`後に`state:RESUMED`になります。このように状態が遷移していくことが、このログから観察できます。

## それぞれの状態について
### INITIALIZED
Activityのインスタンスができているだけで画面などの用意はできていない状態。

### CREATED
onCreate()によって画面などはできている(ActivityでViewが作られている)が、表示されてもおらず、フォーカスも持っていない状態。
(画面ができていると言っても表示するためのViewができているだけで、APIなどからデータを取得して表示するときには表示する内容がない場合もある。)

どういうときにこの状態のままになるか？
→ Activityが別のActivityの裏にいて、画面が隠れているとき、この状態のままになる。

### STARTED
画面が表示されているが、フォーカスを持っていない状態

どういうときにこの状態のままになるか？
→ Activityが他の透過Activityの後ろに表示されているとき、マルチウインドウで他のActivityにフォーカスがあたっている(選択されている)とき。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/7db0b5c3-3817-e854-a638-65ef31073c15.png)

### RESUMED
画面がフォーカスを持っている状態。

どういうときにこの状態のままになるか？
→ 普通にActivityが最前面で表示されているとき。

## それぞれのメソッドについて

それぞれonCreate()、onStart()、onResume()について説明していきます。onPause()などのメソッドについてもこの中で触れます。

## onCreate()
INITIALIZEDからCREATEDに変わるときに呼ばれる。

### タイミング
Activityのインスタンスが作られたときに一度だけ呼び出されます。
逆にonDestroy()はActivityのインスタンスが破棄されるときに一度だけ呼ばれます。

### 何をするか？
通常、ここでActivityで表示するViewを作成`setContentView(R.layout.activity_main)`を行ったりして、Activityで必要になるものを作っていきます。

## onStart()
CREATEDからSTARTEDに変わるときに呼ばれる。

### タイミング
画面が表示されたタイミングで発火されます。
例えばMainActivityからDetailActivityに遷移していて、戻るボタンでMainActivity戻ってきたときには、MainActivityのインスタンスはメモリ不足で破棄されていなければそのまま使われるので、onCreateは呼ばれず、onStartから呼ばれることになります。
逆に画面が表示されなくなったらonStop()が呼び出されます。
### 何をするか？
例えば動画プレイヤーであれば画面が表示されていないのに動画を再生するのは微妙ですよね？そういうときにonStart()で動画の再生を開始しておいて、onStopで動画を止めるようにすれば、うまく動作させることができます。

## onResume()
STARTEDからRESUMEDに変わるときに呼ばれる。

### タイミング
画面がフォーカスを持ったタイミングで発火されます。
Activityは透過させることができ、裏に以前に表示されていたActivityを表示させておくことができたりします。その場合に表示はされているのですが、フォーカスを持っていない状態になります。
また、Androidにはマルチウインドウという機能があり、それを使っている場合は画面に複数のActivityが表示されます。その場合にはフォーカスされている画面でだけフォーカスを持ちます。(Android 10から一部仕様が変わりました)
逆にフォーカスを失ったときにonPauseが呼ばれます。
onPostResume()はonResumeの後に呼ばれます。

### 何をするか？
ログ計測などで使われたりはよくありますが、そこまで頻繁には使われるメソッドではないです。フォーカスがあたったときに更新したいなどは行うことができます。

# 2. 画面回転とViewModel
Androidのライフサイクルの難しいところと言われる画面回転について記述しておきます。
**Androidでは画面回転するとActivityのインスタンスが作り直されます。**

```
アプリの起動起動
onCreate state:INITIALIZED
onStart state:CREATED
onResume state:STARTED
onPostResume state:RESUMED
--- ここで画面回転開始
onPause state:STARTED
onStop state:CREATED
onDestroy state:DESTROYED ← 一度Activityのインスタンスが作り直される
onCreate state:INITIALIZED
onStart state:CREATED
onResume state:STARTED
onPostResume state:RESUMED
```

## 画面回転すると何が困るのか？
こんな感じのTextViewがあったとしましょう。

```xml
    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="0"
        android:textSize="100sp"
        />
```

これをクリックで+1ずつしていくコードを書いたとしましょう。

```kotlin
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val textView = findViewById<TextView>(R.id.text)
        textView.setOnClickListener {
            textView.text = (textView.text.toString().toInt() + 1).toString()
        }
```

画面回転で初期化されます😇

![screenrotate.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/f8b26dc8-8c69-946e-80fb-fe6c07d9d07e.gif)

Activityにデータを保持しておくとActivityのインスタンスごと作り直されるので、このような現象が起きます。
(FragmentというActivity内の部品を表すコンポーネントがあり、この[Fragmentで画面回転での破棄を無効にするオプションがあるのですが、deprecatedになりました](https://android-review.googlesource.com/c/platform/frameworks/support/+/1159084))

## onSaveInstanceState()を使った解決策
onSaveInstanceState()はonStop()の前に呼び出されるメソッドです。
savedInstanceStateはActivityが破棄されるときにデータを保存しておいて、取り出すことができます。
具体的にはonSaveInstanceStateでbundleにデータを入れ、onCreateの引数のsavedInstanceStateで取り出す形になります。
savedInstanceStateは画面回転だけでなく、アプリケーションのプロセスが破棄されても、[システムサービスでデータが保持されている](https://qiita.com/ntsk/items/6eafe7bd7edbab61341e#bundle%E3%81%AF%E3%81%A9%E3%81%93%E3%81%AB%E4%BF%9D%E5%AD%98%E3%81%95%E3%82%8C%E3%82%8B)ため、かなり強力な解決策です。

```kotlin
class MainActivity : AppCompatActivity() {
    lateinit var textView: TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        textView = findViewById<TextView>(R.id.text)
        textView.setOnClickListener {
            textView.text = (textView.text.toString().toInt() + 1).toString()
        }
        if (savedInstanceState != null) {
            textView.text = savedInstanceState.getString("count")
        }
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        outState.putString("count", textView.text.toString())
    }
```

ただ、この方法にもデメリットがあります。
大きすぎるデータを保存すると`TransactionTooLargeException`になってしまうため、ユーザーのリストや画像などには適していません。
また1つ1つデータを保存していくのは結構骨が折れます。

## ViewModelを使った解決策

ViewModelはArchitecture Componentと言われるライブラリの一部で、このデータの保持を楽にしてくれるコンポーネントです。
よくJetpackやAndroidXという言葉が出てくると思うので、以下に自分の理解している構造を書いておきます。つまりAndroid Jetpackの中にあるAndroidXの中にあるArchitecture Componentの中にあるViewModel。。です。(間違っていたら教えて下さい)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/4817e8ab-4b70-30a7-cdd5-915bd98723f1.png)

ViewModelはActivityと違って画面回転を通じて生き残ります。つまり、いちいち保存したりしなくてもViewModelでデータを保持させることでうまく動作させることができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/fbeea47e-0123-526b-f949-65d07ecb4c43.png)
https://developer.android.com/topic/libraries/architecture/viewmodel?hl=en より

ViewModelはViewModelProviderから取得しており、これにより画面回転してアクセスする元のActivityのインスタンスが変わっても同じViewModelのインスタンスを取得することができます。

```kotlin
class MainViewModel : ViewModel() {
    var count: Int = 0
}

class MainActivity : AppCompatActivity() {
    lateinit var textView: TextView
    lateinit var viewModel: MainViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        textView = findViewById<TextView>(R.id.text)
        viewModel = ViewModelProvider(this).get(MainViewModel::class.java)

        textView.text = viewModel.count.toString()
        textView.setOnClickListener {
            viewModel.count++
            textView.text = viewModel.count.toString()
        }
    }
```
![lifecycle.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/37e7b5f5-8e2b-3a07-d8ed-177724d93883.gif)

画面回転を克服して実装していくことができました :tada: 

# 3. ViewModelとLiveData
もうViewModelを使えばAndroidアプリの実装は完璧でしょうか？
カウンターを作る人は多くないと思うので試しにもう少し実用的な例でやっていきましょう。
Androidのアプリではサーバーからデータを取得して表示するようなアプリはかなりよくあるパターンです。

例えば以下のようなAPIからデータを取得するクラスがあったとしましょう。実際は通信して、データを取ってくる形になりますが、ここではスレッドを作ってコールバックでデータを返すだけにします。


```kotlin
class Api {
    fun fetch(url: String, onFetched: (response: String) -> Unit) {
        thread {
            // simulate api call
            Thread.sleep(5000)
            onFetched("$url:fetched")
        }
    }
}
```

(APIが返してきた文字列を表示している例)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/ca1b91e8-de0a-de58-a70e-fc36d9603494.png)

そしてViewModelではViewModelで取得したときに、Activityでデータを受け取れるようにしたとしましょう(※ダメな例なので真似して実装しないでください。)

:x: 

```kotlin
class MainViewModel : ViewModel() {
    val api = Api()
    var response: String? = null

    init {
        fetch()
    }

    private var onResponseChangedListeners: List<((response: String) -> Unit)> = mutableListOf()

    fun addOnResponseChangedListener(onResponseChangedListener: (response: String) -> Unit) {
        this.onResponseChangedListeners += onResponseChangedListener
    }

    private fun fetch() {
        api.fetch("http://api.example.com/hogehoge") { response ->
            this.response = response
            this.onResponseChangedListeners.forEach { it(response) }
        }
    }
}

class MainActivity : AppCompatActivity() {
    lateinit var viewModel: MainViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
        val textView = findViewById<TextView>(R.id.text)
        textView.text = viewModel.response
        viewModel.addOnResponseChangedListener { response ->
            runOnUiThread {
                textView.text = response
            }
        }
    }
}
```

**一見問題なく動くように見えますがこのコードにはいくつかの問題点があります。 :sob:**

## 問題1. Activityがメモリリークしている
ViewModelの生存期間はActivityより長いので、画面回転すると`onResponseChangedListeners`で保持されているので、Activityのインスタンスがリークします。ActivityのインスタンスがリークするとActivityは画面全体を保持しているので、メモリが枯渇するようになります。

↓でActivityのインスタンスが暗黙的に渡されています。

```kotlin
        viewModel.addOnResponseChangedListener { response ->
```

デバッガーでJava heapをdumpしてActivity/Fragment Leaksにチェックを入れるとリークしているインスタンスを教えてくれます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/90028a4a-ce40-1a3d-05a9-671226642fab.png)

一応この問題に関しては、ActivityのonDestroy()でonResponseChangedListenersのlistenerを消すようにすれば一応うまく動きます。

## 問題2. UIへのデータの反映忘れが起こりやすい

addOnResponseChangedListenerではfetch()が終わったときしか反映されないので、画面回転後にActivityが再生成された後にはTextViewに値が反映されません。

```kotlin
        val textView = findViewById<TextView>(R.id.text)
        // **↓の行を忘れても一応動くが画面回転でtextが表示されなくなる**
        textView.text = viewModel.response
        // ** ↑ **
        viewModel.addOnResponseChangedListener { response ->
            runOnUiThread {
                textView.text = response
            }
        }
```

## 問題3. onStop以降でも`addOnResponseChangedListener`のコールバックが呼ばれる
APIの応答に5秒かかっている間に普通にホームボタンが押されたりする可能性があります。そのときにActivityはCREATED状態になります。
AndroidではonStopとonDestoryの間、CREATED状態の時に呼び出すとクラッシュするAndroidのFrameworkのAPIが存在します。(幸い、TextViewへのsetでは大丈夫です。)例えばこのタイミングでFragmentをレイアウトに追加する(FragmentTransactionのcommit()など)とクラッシュが発生します。
これを対策するには今の状態をみて、**次のonStart以降で処理を動かすなどかなり工夫が必要になります。** :innocent: 
昔は以下のような処理をキューにためておいて、onResume以降で処理するなどを頑張ってして、なんとかしていました。
https://stackoverflow.com/a/8122789/4339442

## LiveDataを使った解決策

自分で上記の問題をそれぞれ対応していくのはかなかな大変です。
そこでArchitecture ComponentのLiveDataは上記の問題を解決するものになります。LiveDataはobserve(観測)できるAndroidのライフサイクルを考慮したデータホルダーとなります。
MutableLiveDataは変更可能なLiveDataで`setValue()`や`postValue()`を呼ぶことで変更することができます。`setValue`は`MainThread`で値を入れるとき、`postValue`は`MainThread`以外から呼ばれるときに利用します。
`MutableLiveData`の親クラスにLiveDataクラスがあります。これはデータの変更を`observe()`を呼ぶことで観測することができます。
以下の例ではfetch()でデータを_responseにデータをセットして、MainActivity内でデータを`observe()`することで反映しています。

```kotlin
class MainViewModel : ViewModel() {
    val api = Api()
    private val _response: MutableLiveData<String> = MutableLiveData()
    val response: LiveData<String> get() = _response

    init {
        fetch()
    }

    private fun fetch() {
        api.fetch("http://api.example.com/hogehoge") { response ->
            _response.postValue(response)
        }
    }
}

class MainActivity : AppCompatActivity() {
    lateinit var viewModel: MainViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
        val textView = findViewById<TextView>(R.id.text)
        viewModel.response.observe(this, Observer { response ->
            textView.text = response
        })
    }
}
```

実際にどのように上記の問題を解決しているのか見ていきましょう。

* 問題1. LiveDataが"Activityがメモリリークしている"に対してどうやって対応しているか？

observeメソッドに`this`を渡しています。ライフサイクルを保持するActivityやFragmentは`LifecycleOwner`というinterfaceを実装しています。**LiveDataはライフサイクルがDESTROYED に変わったときにオブザーバーを削除してくれます。**そのため、メモリリークが発生しません。

```kotlin
        viewModel.response.observe(this, Observer { response ->
            textView.text = response
        })
```

* 問題2. LiveDataが"UIへのデータの反映忘れが起こりやすい"に対してどうやって対応しているか？

**LiveDataをobserve()したとき、ライフサイクルがonStart以降になったときに、オブザーバーを呼び出してくれます。**
そのため画面回転後にonCreate()が呼ばれ、そこで`observe()`した場合に、ちゃんと呼び出してくれるのでデータを反映することができます。
そのため、データが変更されていなくても呼び出されるので、onCreateメソッドの中でViewModelが保持しているLiveDataのデータを直接見ることなしに、UIを変更していくことができます。

* 問題3. LiveDataが"onStop以降でも`addOnResponseChangedListener`のコールバックが呼ばれる"に対してどうやって対応しているか？

**observeしたときに渡すActivityやFragment(`LifecycleOwner`)のライフサイクルがSTARTED以降でないと変更を呼び出さないので、問題ありません。**

他にもLiveDataはいくつかの問題を解決してくれます。Android Developerを確認してみましょう。
https://developer.android.com/topic/libraries/architecture/livedata?hl=ja

# 4. ViewModelとKotlin Coroutiens

ここまでで、一応いい感じに動くアプリができました。
ただ、まだ残念ながら少し問題は残っています。

## 問題1. ViewModelのリーク

以下を実行したときに、スレッドによってコールバックが保持されてしまっています。もしコールバックがずっと呼び出されなければ、ViewModelのインスタンスが破棄されなかったり、ViewModelはもう使われないのに不要に呼び出されてしまったりします。

```kotlin
    private fun fetch() {
        api.fetch("http://api.example.com/hogehoge") { response ->
            _response.postValue(response)
        }
    }
```

## 問題2. コールバック地獄
DBから読み出して、なければAPIから取得したい場合はどうでしょうか？行おうとすると以下のようになります。このようにコールバックが深くなっていくことをコールバックヘルといいます。この場合はそんなにはわかりにくくないですが、実際にはもっと複雑になっていきます。 (本来このような処理はRepositoryなどに分離するのが普通ですが、分離してもこの問題は残ります。)

```kotlin
class MainViewModel : ViewModel() {
    val db = Db()
    val api = Api()
    private val _contents: MutableLiveData<String> = MutableLiveData()
    val contents: LiveData<String> get() = _contents

    init {
        fetch()
    }

    private fun fetch() {
        db.read { contents: String? ->
            if(contents != null) {
                _contents.postValue(contents)
                return@read
            }
            api.fetch("http://api.example.com/hogehoge") { response ->
                _contents.postValue(response)
            }
        }
    }
}
```

## 問題3. メインスレッドかどうかを気にしたプログラミングが必要となる

LiveDataはメインスレッド以外で値をセットするにはpostValueを使う必要があります。コールバックなどで今はメインスレッドか？などを気にしながらプログラミングしていく必要があります。

```kotlin
    private fun fetch() {
        api.fetch("http://api.example.com/hogehoge") { response ->
// **このコールバック内はメインスレッドではないため、
// ここでsetValue()ではなく、postValue()を使わないとクラッシュする**
            _response.postValue(response) 
        }
    }
```


## Kotlin Corotuinesを使った解決策
Coroutinesは非同期処理のデザインパターンで、Kotlinに実装されたものがKotlin Coroutinesです。
AndroidはFirst Class Coroutines Supportしています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/217583a8-8dd4-165c-d235-2c972b6ab869.png)


https://www.youtube.com/watch?v=BOHK_w09pVA

これにより上記の問題が解決できます。

Coroutinesのコードを理解するにはいくつか理解しなくてはいけない概念が存在します。

### Coroutinesの中断と再開

コルーチンを使うと、`launch{}`の中で以下のようにメインスレッドを使うコードとAPIの呼び出しをするようなコードを混ぜて書くことができます。

```kotlin
launch {
    progress.isVisible = true
    val result = api.fetch()
    progress.isVisible = false
    show(result)
}
```

Androidのメインスレッドで普通に上記のようなコードを書くと、メインスレッドを通信中にブロックしてアプリがタップしても何をしても反応しなくなり、フリーズ状態になります。Application Not Responding(ANR)が発生します。
適切に実装されたコルーチンのメソッドであれば、このコードでANRなどの問題は起こりません。
なぜならKotlin Coroutinesには中断、再開という概念があるからです。
具体的にはこのapi.fetch()を呼んだときにCorotuinesを`中断`状態に入り、中断に入っている間は他のタップしたときの反応などメインスレッドを使う処理を実行させることができ、fetch()が終わったときにまたこの`api.fetch()`の次の行に戻ってきて、メインスレッドで処理の続きをできる、`再開`できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/99dcff7f-e23e-e045-a4e4-8494b874b130.png)
Google I/O 2019より

またCoroutinesを`launch()`するとJobのインスタンスが取得でき、cancel()を呼ぶことで、途中で処理を止めることができます。

```kotlin
val job = launch {
    progress.isVisible = true
    val result = api.fetch()
    progress.isVisible = false
    show(result)
}

// 不要になったらキャンセルする
job.cancel()
```


### Coroutines Scope

先程のコードの例は少し間違っており、実際はコルーチンスコープがないとコルーチンは`launch()`メソッドを呼ぶことができません。
Kotlin Corotuinesは構造化することができ、親のJobをCoroutiensScopeに渡して作成し、CoroutineScopeのcancelを呼ぶことで、子のCoroutinesを全てキャンセルしていくことができます。

```kotlin
val scope = CoroutineScope(Job())
scope.launch {
...
}
scope.launch {
...
}

// 不要になったらcancelする
scope.cancel()
```

### Coroutines Dispatcher

実際どのスレッドで処理が実行されるのかが気になると思います。
以下のように書くことで途中でスレッドを切り替えて処理することができます。

Dispatchers.MAIN = メインスレッド(AndroidではUIを触る)
Dispatchers.IO = I/O関連を処理するためのスレッドが利用される
Dispatchers.DEFAULT = それ以外の計算系に利用される


```kotlin
scope.launch {
    progress.isVisible = true
    val result = withContext(Dispatchers.IO){
        URL("").openConnection().getInputStream()...
        ...
    }
    progress.isVisible = false
    show(result)
}
```

これをただメソッドに分けて書くと以下のようになります。このsuspend functionとは、中断可能なメソッドという意味です。勘違いしてほしくないのが、このsuspendを使ったからといって勝手にバックグラウンドスレッドになったりしないということです。

```kotlin
        val scope = CoroutineScope(Job())
        scope.launch {
            progress.isVisible = true
            val result = fetchApi()
            progress.isVisible = false
        }
    }

    private suspend fun fetchApi(): String {
        return withContext(Dispatchers.IO) {
            URL("").openConnection().getInputStream()...
            ...
        }
    }
```

### Kotlin Corotuinesを使った解決策のコード

これを利用したKotlin Corotuinesを使った解決策では以下のようになります。
viewmodel-ktx 2.1.0を使うとviewModelScopeというものが用意されており、これを使うことで、ViewModelが破棄されるときにCorotuinesScopeをキャンセルすることができます。

```kotlin
class Db {
    suspend fun read(): String? {
        return withContext(Dispatchers.IO) {
            // simulate db read
            delay(5000)
            null
        }
    }
}

class Api {
    suspend fun fetch(url: String): String {
        return withContext(Dispatchers.IO) {
            // simulate api call
            delay(5000)
            "$url:fetched"
        }
    }
}

class MainViewModel : ViewModel() {
    val db = Db()
    val api = Api()
    private val _contents: MutableLiveData<String> = MutableLiveData()
    val contents: LiveData<String> get() = _contents

    init {
        fetch()
    }

    private fun fetch() {
        viewModelScope.launch {
            val contents = db.read()
            if (contents != null) {
                _contents.postValue(contents)
                return@launch
            }
            val response = api.fetch("http://api.example.com/hogehoge")
            _contents.value = response
        }
    }
}
```

このコードがどのように問題を解決するのかを見ていきましょう。

* Kotlin Corotuiensが"問題1. ViewModelのリーク"に対してどうやって対応しているか？
`viewModelScope`がキャンセルされることによって、コルーチンがキャンセルされるので、問題なく動作します。

* Kotlin Corotuiensが"問題2. コールバック地獄"に対してどうやって対応しているか？
Kotlin Coroutinesの中断、再開によってコールバックなしに非同期処理をコーディングしていくことができます。

* Kotlin Corotuiensが"問題3. メインスレッドかどうかを気にしたプログラミングが必要となる"に対してどうやって対応しているか？
`viewModelScope`はメインスレッドで実行され、明示的に切り替えなければ基本的にメインスレッドで行われるため、APIコールの後であっても今のスレッドを気にせずにコーディングしていくことができます。


### 実際どのようにKotlin CoroutinesでAPIやDBを呼び出すメソッドを実装していったら良いのか？
APIやDB呼び出しのコードがdelay()などを使ったデモコードになっておりわかりにくかったと思います。
実際にはRetrofitやRoomはsuspend functionに対応しているので、自動的にsuspend functionを定義しておくことで実装を生成してくれるため、問題なく実装できます。
またもしそのような方法が提供されていなくても以下のように`suspendCancellableCoroutine`を利用することで問題なく実装できます。

```kotlin
    suspend fun fetch(): String {
        return suspendCancellableCoroutine<String> { cancellableContinuation ->
            myApi.fetch( // 自分で用意したAPI
                    onSuccess = { result: String ->
                        cancellableContinuation.resume(result)
                    },
                    onFailure = { e: Throwable ->
                        cancellableContinuation.resumeWithException(e)
                    }
            )
            cancellableContinuation.invokeOnCancellation {
                // キャンセル処理
                myApi.cancelFetch()
            }
        }
    }
```

またはブロッキングして取得する方法があるのであれば以下のような方法も使えます。(CoroutinesはキャンセルしたときにThreadをintrerrupedしないので処理が途中でキャンセルされないので注意が必要です)

```kotlin
    suspend fun fetch(): String {
        return withContext(Dispatchers.IO) {
            api.blockingFetch()
        }
    }
```


# まとめ
Androidのライフサイクルの基礎的なところから、画面回転の問題をViewModelで解決し、データを監視する問題をLiveDataで解決、非同期処理の問題をKotlin Coroutinesで解決していくことができました。
実際にAndroid Developerには以下のようにそれぞれの問題について書かれています。それをつないで書いてみたのが今回の記事になります。そのため、部分的にわかりにくい部分があれば、以下を参照していただけると結構わかっていくのではないかと思います。

ViewModel
https://developer.android.com/topic/libraries/architecture/viewmodel?hl=ja
LiveData
https://developer.android.com/topic/libraries/architecture/livedata?hl=ja
Coroutines
https://developer.android.com/kotlin/coroutines