はじめに
非同期処理をCoroutineで行って、LiveDataで変更の通知を受け取る。
ということはよくあることかと思います。
この記事では、CoroutineとLiveDataの処理を別々に行っていたものを
ひとまとめにして行う liveData{} の実用例を書きたいと思います。
便宜上、
CoroutineとLiveDataを別々で処理する方法を Coroutine + LiveData
CoroutineとLiveDataをまとめて処理する方法を LiveDataScope
と定義します。
(公式では単にliveDataですが、わかりにくいのでLiveDataScopeにさせてください)
LiveDataScopeを使用する場合、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 での実装
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))
}
})
}
}
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 での実装
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))
}
})
}
}
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
CoroutineLiveData