2025年4月19日土曜日

Android そういえば Kotlin Coroutine ってどう使うのが良いの?

どうも。どっことです。今回はKotlinCoroutineのテーマです。

Kotlin Coroutine

Kotlin Coroutineは非同期処理などのコールバック地獄を解消してくれたり、可読性の高いコードを実装できるようになる便利な機能ですが、「実際のところ、どう使うのが良いの?」と考えました。今回はそんな使うと便利だけど実際どう使うかイマイチ分からない人向けに、ベストプラクティスのページの内容を簡単に紹介したいと思います。

TL;DR(結論)

ViewModelを実装してviewModelScope.launch/View側ではlifecycleScope.launchを使うのがよさそうです。

Google開発者サイトでコルーチンに関するベストプラクティスが紹介されています。コルーチンにフォーカスしたものだと、以下が挙げられています。

  1. Dispatcherを外から注入できるようにする
  2. ViewModelでコルーチンを作成する
  3. GlobalScopeは使わない
  4. コルーチンをキャンセルできるようにする

Dispatcherを外から注入できるようにする

withContextなどで引数に設定するDispatcher(どのスレッドで処理するかの指定)は、ハードコードせず外から注入できる形にした方が良いです。テストコードを実装するときにテストが容易にできるようになります。

// OK:処理するDispatcherを外から指定する
class SampleRepository(private val ioDispatcher: CoroutineDispatcher =  Dispatchers.IO) {
    suspend fun veryHeavyTask() = withContext(ioDispatcher) {
        // very heavy task
    }
}
class SampleRepository() {
    // NG: Dispatcherをハードコード(直接指定)しない
    suspend fun veryHeavyTask() = withContext(Dispatchers.IO) {
        // very heavy task
    }
}

ViewModelでコルーチンを作成する

ViewModelを作成し、そこでコルーチンを作成→非同期処理を実装するのがよいとのことです。

そして画面側に非同期処理が必要なことを意識させることがないよう、suspend関数として公開しないよう実装するのが良いとのことです。

class SampleViewModel(): ViewModel() {
    private val repository = SomeRepository()

    // NG:UI側にsuspend関数(=非同期処理)として公開しない
    suspend fun badSample() = repository.veryHeavyTask()

    // OK:ViewModel内でコルーチンを生成し非同期処理を実行する
    fun sample() {
        viewModelScope.launch {
            repository.veryHeavyTask()
        }
    }
}

GlobalScopeを使わない

テストコードの実装しにくさ、GlobalScopeをハードコードしていることによりDispatcherもハードコードされやすくなる点などから、GlobalScopeを利用しないことを推奨しています。

class SampleRepository() {
    fun veryHeavyTask() {
        // NG:GlobalScopeをハードコードしない
        GlobalScope.launch {
            // very heavy task
        }
    }
}

GlobalScopeの利用が正当であるケースはかなり稀で、アプリが生存期間中にアクティブな状態を維持しなければいけない状態とのことです。また、現在のスコープより長い生存期間が必要な処理であれば、CoroutineScopeを外部から指定するような構成とすることを検討すべきとのことです。

コルーチンをキャンセル可能にする

コルーチンはキャンセル可能ですが、実際コルーチン内でキャンセルされたか確認したり、停止したりするまではコルーチンはキャンセルされないとのことです。ensureActiveを使用することでキャンセルの確認ができます。

class SampleViewModel(): ViewModel() {
    private val repository = SampleRepository()

    fun sample(value: Int) {
        viewModelScope.launch { 
            val result = repository.validateValue(value)
            // キャンセルできるタイミングでアクティブか確認
            ensureActive()
            if (result) {
                repository.submit(value)
            }
        }
    }
}

まとめ

今回はKotlinCoroutineのベストプラクティスについて紹介しました。より詳しい内容は参考に記載したGoogleのベストプラクティスの記事からさまざまなページにアクセスできるので、ぜひ参考にしてください。

参考

移行予定

どうも。どっことです。 タイトルの通りですが、諸事情により GitHubPage に移行予定です。 https://mkt120.github.io/ この備忘録に記載の内容を転記しつつ、今後はこちらのページを更新していく予定です。