Kotlin coroutine入門 ③~ViewModelの中でcoroutineを扱ってみる~


Kotlin coroutine入門 ②の続きです。

前回の入門記事から、時間が経っていますが、今回はViewModelの中で、何らかのAPIと通信する体のcoroutineを実装しUIに反映して行きたいと思います!!

従来の構成 MVVMRxとcoroutine比較

Rx使用時

coroutine使用した場合

observableがまるっとcoroutinesに置き換わります。実際はReactiveStreamに似た、Coroutine flowでしょう。

実装

実際に実装をしてみます。(Repositry,ApiClientはApi.ktとして実装しています)

Api.kt
package ***

import kotlinx.coroutines.*

class Api(){
     suspend fun isHoge (): Deferred<Boolean> = coroutineScope {
            async (context = Dispatchers.IO) {
                true
            }
     }
}
MainActivityViewModel.kt
package ***

import android.util.Log
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlin.coroutines.CoroutineContext

class MainActivityViewModel() : ViewModel(), CoroutineScope, LifecycleObserver {


    private val job = Job()

    // Dispatcher.Main を指定しているためこのスコープで起動するコルーチンはメインスレッドで動作する。
    // Job として上で定義した job を渡しているので、すべてのコルーチンはこの job の子になる。
    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + job

    val scope = CoroutineScope(coroutineContext)

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    fun onResume() {
        val api  = Api()
        //main threadで実行する
        scope.launch {
            try {
                api.isHoge().await().let {
                    Log.d("MainActivityViewModel", it.toString())
                }
            }catch (e: Throwable){
                Log.d("MainActivityViewModel", e.message)
            }
        }
        Log.d("MainActivityViewModel","onResumeEnd")
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    fun onPause() {
        //jobに属している全てのtaskを停止する
        job.cancel()
    }
}

Jobキャンセルについて

親ジョブがキャンセルされると子Job は子にキャンセルを伝播します。
キャンセルを忘れると、リークする可能性があります。

MainActivityViewModel.kt

    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    fun onPause() {
        //jobに属している全てのtaskを停止する
        job.cancel()
    }

こちらが参考になりました。

ViewModel KTX

ViewModel KTXの viewModelScope() により、Coroutine Scopeやキャンセルが不要となりました。
実際のコードは以下の通りです。

(app)build.gradle
 dependencies {
        implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
    }

MainActivityViewModel.kt
package  ****

import android.util.Log
import androidx.lifecycle.*
import kotlinx.coroutines.launch


class MainActivityViewModel() : ViewModel(), LifecycleObserver {

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    fun onResume() {
        val api  = Api()
        //main threadで実行する
        Log.d("MainActivityViewModel","onResumeStart")
        viewModelScope.launch {
            try {
                api.isHoge().await().let {
                    Log.d("MainActivityViewModel", it.toString())
                }
            }catch (e: Throwable){
                Log.d("MainActivityViewModel", e.message)
            }
        }
        Log.d("MainActivityViewModel","onResumeEnd")
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    fun onPause() {
        //job cancel は不要
    }
}

コードもすっきりしましたし、cancelが漏れることもないでしょう。

まとめ

viewModelScopeが登場しキャンセル漏れも防げるようになりましたね。
この辺りはRxの時もDispose忘れなどあったので、すごく助かります。
次回はRxのReactiveStreamでおなじみである、coroutine channel flowについて
記載します。