Android Lifecycle + Kotlinx Coroutines

## AndroidのライフサイクルとCoroutineScope
またです｡
前書いた記事よりスマートに､`async-await`と例外処理とかは割愛です｡
個人的には `1.3` から追加された `Result`型をゴニョゴニョしてやってます｡

[前書いたやつ](https://qiita.com/naito-y/items/c3d286585a7c9fb72372)

前回書いたあれ､なかなかつらい感じだったのでもっといい感じにならないかと思ってたら [SupervisorJob](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-supervisor-job.html)あるやんみたいな(今更)｡
この `SupervisorJob`その名の通り監督で､ その子どもの`Job`が失敗しても他の子供に影響が無く､親が子供の失敗で死ぬことも無いらしい｡英語苦手だけど多分きっとそう｡  
親を殺せば子も死ぬので丁度いい､`onDestroy`で`SupervisorJob`殺せばいいだけだね!


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

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

## コードと解説 

### AndroidScope

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

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

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

### LifecycleScope
次に､`CoroutineScope` を元に作成される `Android Architecture Component`の`Lifecycle`用の`CoroutineScope`を定義｡  

```kotlin
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`を定義
`import`は`LifecycleScope`と一緒だと思う

```kotlin
interface LifecycleScopeSupport {
    val scope: LifecycleScope
}

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

```

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

### 使うとき

```kotlin
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::onDestroy`で`SupervisorJob::cancelChildren`呼べばいいだけ｡
でも呼び忘れで消耗したくないので`Lifecycle`に頼ります(忘れっぽいので)｡


## 余談
スコープのスペルって`Scope`だったっけ･･･? ずっと見てたらなんか不安になってきた｡

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

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

```kotlin
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)

```

## 参考
https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-supervisor-job.html