7
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Android Lifecycle + Kotlinx Coroutines

Last updated at Posted at 2018-12-10

AndroidのライフサイクルとCoroutineScope

またです。
前書いた記事よりスマートに、async-awaitと例外処理とかは割愛です。
個人的には 1.3 から追加された Result型をゴニョゴニョしてやってます。

前書いたやつ

前回書いたあれ、なかなかつらい感じだったのでもっといい感じにならないかと思ってたら SupervisorJobあるやんみたいな(今更)。
この SupervisorJobその名の通り監督で、 その子どものJobが失敗しても他の子供に影響が無く、親が子供の失敗で死ぬことも無いらしい。英語苦手だけど多分きっとそう。
親を殺せば子も死ぬので丁度いい、onDestroySupervisorJob殺せばいいだけだね!

とりあえずコードと解説貼ってきま。

(2018 12/12 AM 3:12 追記) `LifecycleScope`について、既に`Job`を持っている`CoroutineScope`を受け入れる状態になっていたため、受け入れないように修正しました。 おまけで書いたコードも気になったところを修正。

コードと解説

AndroidScope

まずはAndroid用のCoroutineScopeを定義。
インスタンスをいっぱい作る必要も無いのでobjectにします。

import kotlinx.coroutines.*
import kotlin.coroutines.*

object AndroidScope : CoroutineScope {
    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main
}

LifecycleScope

次に、CoroutineScope を元に作成される Android Architecture ComponentLifecycle用のCoroutineScopeを定義。

import androidx.lifecycle.*
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext

class LifecycleScope(
        private val lifecycleOwner: LifecycleOwner,
        coroutineScope: CoroutineScope = AndroidScope
) : LifecycleObserver, CoroutineScope {

    init {
        coroutineScope.coroutineContext[Job]
                ?.let { throw IllegalArgumentException("A Context with Job already passed") }

        lifecycleOwner.lifecycle.addObserver(this)
    }

    private val mJob = SupervisorJob()

    override val coroutineContext: CoroutineContext = (coroutineScope + mJob).coroutineContext

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    fun onDestroy() {
        mJob.cancelChildren()
        lifecycleOwner.lifecycle.removeObserver(this)
    }
}

fun LifecycleScope.bindLaunch(
        start: CoroutineStart = CoroutineStart.DEFAULT,
        block: suspend CoroutineScope.() -> Unit
) = launch(coroutineContext, start, block)

onDestroy()SupervisorJobの子を殺してからLifecycleOwnerから自身を外すだけ。

fun LifecycleScope.bindLaunch() は無くてもいいけど一応定義。
CoroutineScopeコンストラクタから渡すようにしているのは、テスト走らせるのときとかにAndroidScopeは使えないからです。

LifecycleScopeSupport

このままだとActivityとかで使いづらいので使いやすくするためにそれ用のinterfaceを定義
importLifecycleScopeと一緒だと思う

interface LifecycleScopeSupport {
    val scope: LifecycleScope
}

fun LifecycleScopeSupport.bindLaunch(
        start: CoroutineStart = CoroutineStart.DEFAULT,
        block: suspend CoroutineScope.() -> Unit
) = scope.bindLaunch(start, block)

LifecycleScopeSupportを任意のActivityに実装すればあとはbindlaunch{} みたいにするだけでライフサイクルにバインドされたJobが起動する。

使うとき

import androidx.appcompat.app.AppCompatActivity

class SampleActivity : AppCompatActivity(), LifecycleScopeSupport {
    override val scope = LifecycleScope(this)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //setContentViewとかごにょごにょ
        bindLaunch { 
            //なんかごにょごにょ
        }
    }
}

楽ちん

LifecycleScope::launch とかでも同じ結果得られると思うけど僕はこっちのが好みです(ほんとにただの好み)。

まとめ

ぶっちゃければこんなの作らなくてもActivity::onDestroySupervisorJob::cancelChildren呼べばいいだけ。
でも呼び忘れで消耗したくないのでLifecycleに頼ります(忘れっぽいので)。

余談

スコープのスペルってScopeだったっけ・・・? ずっと見てたらなんか不安になってきた。

追加でおまけ

LifecycleScopeからonDestroy()漏れてるのが気に食わない人向け(僕です)

(2018 12/12 AM 3:12 追記)
LifecycleObserverが漏れてるのも気になった。

import androidx.lifecycle.*
import kotlinx.coroutines.*
import kotlin.coroutines.*

interface LifecycleScope : CoroutineScope

private class LifecycleScopeImpl(
        private val lifecycleOwner: LifecycleOwner,
        coroutineScope: CoroutineScope
) : LifecycleScope, LifecycleObserver {

    init {
        coroutineScope.coroutineContext[Job]
                ?.let { throw IllegalArgumentException("A scope with a Job already passed") }

        lifecycleOwner.lifecycle.addObserver(this)
    }

    private val mJob = SupervisorJob()

    override val coroutineContext: CoroutineContext = (coroutineScope + mJob).coroutineContext

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    fun onDestroy() {
        mJob.cancelChildren()
        lifecycleOwner.lifecycle.removeObserver(this)
    }
}

@Suppress("FunctionName")
fun LifecycleScope(
        lifecycleOwner: LifecycleOwner,
        coroutineScope: CoroutineScope = AndroidScope
): LifecycleScope = LifecycleScopeImpl(lifecycleOwner, coroutineScope)

参考

7
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?