LiveDataでCoroutineを使ってみた

#はじめに
非同期処理を`Coroutine`で行って、`LiveData`で変更の通知を受け取る。
ということはよくあることかと思います。

この記事では、`Coroutine`と`LiveData`の処理を別々に行っていたものを
ひとまとめにして行う `liveData{}` の実用例を書きたいと思います。

便宜上、
`Coroutine`と`LiveData`を別々で処理する方法を `Coroutine + LiveData`
`Coroutine`と`LiveData`をまとめて処理する方法を `LiveDataScope`
と定義します。
(公式では単に`liveData`ですが、わかりにくいので`LiveDataScope`にさせてください)

`LiveDataScope`を使用する場合、gradleに以下の設定が必要になります。

```build.gradle
dependencies {
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-alpha01" // or higher
}
```

それでは早速、見ていきましょう。

#仕様
今回のサンプルの仕様は以下の通りです。

- メイン画面(`MainActivity`)とログイン画面(`LoginActivity`)があります。
- `SharedPreferences`にログイン情報（`loginId`, `password`）が保存されている。
- アプリを起動させたら`MainActivity`が作成されます。
- `MainActivity`の`onCreate()`で`SharedPreferences`に保存されているログイン情報をもとにログインAPIをコールし、照合すればメイン画面のまま居残り、照合しなければログイン画面に遷移する。

今回のサンプルで登場するクラスは以下の2つになります。

- MainActivity
- MainViewModel

また、`Coroutine + LiveData`での実装と`LiveDataScope`での実装を区別するため、
前者には`Before`、後者には`After`の接頭詞をそれぞれのクラスに付与します。

# Coroutine + LiveData での実装

```BeforeMainActivity.kt
class BeforeMainActivity : AppCompatActivity() {

    lateinit var vm: BeforeMainViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_before_main)

        vm = ViewModelProviders.of(this).get(BeforeMainViewModel::class.java)
        vm.login()
        // LiveDataの変更通知を受け取る
        vm.loginStatus.observe(this, Observer { isLogin ->
            if (!isLogin) {
                startActivity(Intent(this, LoginActivity::class.java))
            }
        })
    }
}
```

```BeforeMainViewModel.kt
class BeforeMainViewModel(app: Application) : AndroidViewModel(app), CoroutineScope {
    // useCaseをDIしたり、userIdとpasswordはSharedPreferencesから取得していますが、比較しやすいよう割愛しています。
    ...
    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Default + Job()

    // LiveData
    private val _loginStatus = MutableLiveData<Boolean>()
    val loginStatus: LiveData<Boolean> = _loginStatus

    // Coroutine
    fun login() {
        launch {
            try {
                // ログインAPIを叩き、照合結果を取得する
                val loginInfo = useCase.login(userId, password) // login()はsuspend関数
                _loginStatus.postValue(loginInfo.isLogin)
            } catch (e: Exception) {
                Timber.d(e)
            }
        }
    }
}
```

# LiveDataScope での実装
```AfterMainActivity.kt
class AfterMainActivity : AppCompatActivity() {

    lateinit var vm: AfterMainViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_after_main)

        vm = ViewModelProviders.of(this).get(AfterMainViewModel::class.java)
        vm.loginStatus.observe(this, Observer { isLogin ->
            if (!isLogin) {
                startActivity(Intent(this, LoginActivity::class.java))
            }
        })
    }
}
```

```AfterMainViewModel.kt
class AfterMainViewModel(app: Application) : AndroidViewModel(app) {

    // LiveDataScope
    val loginStatus = liveData {
        try {
            val loginInfo = useCase.login(userId, password)
            emit(loginInfo.isLogin)
        } catch (e: Exception) {
            Timber.d(e)
        }
    }
}
```

#変わった点
- `Activity`から`ViewModel`の`login()`を呼び出さなくて良くなった！
- `VeiwModel`で`CoroutineScope`を実装する必要がなくなった！（そもそも`viewModelScope`使えば必要ないが…）
- 今までイチイチ`ViewModel`で設定していた`LiveData`プロパティの記述がなくなった！
- `LiveData`への変更通知が`postValue()`でなく`emit()`で行える！
- `AfterMainViewModel`がイイ感じにスッキリ！

#おわりに
コードの記述量が減るのはいいことですね。

#参考
[Use Kotlin coroutines with Architecture components](https://developer.android.com/topic/libraries/architecture/coroutines)
[CoroutineLiveData](https://android.googlesource.com/platform/frameworks/support/+/7a4525ac3c56adff5e0de713ca54eae89518e28c/lifecycle/livedata/ktx/src/main/java/androidx/lifecycle/CoroutineLiveData.kt)