androidx.lifecycle:lifecycle-viewmodel-ktx
是ViewModel的KTX扩展库,可以让我们在ViewModel中方便的使用Coroutine。对于ViewModel-KTX的使用,我们需要关注以下三个知识点:
接下来通过一个MVVM架构的示例,围绕上面这三点进行一个简单介绍。
我们会在Model层使用Repository进行管理,在ViewModel层通过LiveData驱动UI更新。
androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0-rc03
Coroutine:1.3.3
ViewModel-KTX中提供了一个ViewModel的扩展函数viewModelScope
,定于如下:
/**
* [CoroutineScope] tied to this [ViewModel].
* This scope will be canceled when ViewModel will be cleared, i.e [ViewModel.onCleared] is called
*/
val ViewModel.viewModelScope: CoroutineScope
viewModelScope可以在ViewModel.onClear
的时候调用CoroutineScope.cancel
,从而中断所有运行中的Coroutine以避免泄露。
viewModelScope会将挂起函数dispatch到MainThread运行,所以可以直接调用LiveData.setValue
val userLiveData = MutableLiveData(...)
viewModelScope.launch {
val user = userRepository.getUser() // 调用suspend函数
userLiveData.setValue(user) // 主线程运行,所以可以setValue
// userLiveData.postValues(user)
}
使用viewModelScope时,无需使用LiveData.postValue
通过suspend
函数可以进行API请求的耗时操作,Retrofit
以及Room
三方库在新版本中都增加了suspend函数的API。我们定义Repository如下,通过Retrofit返回suspend接口:
interface UserApi {
suspend fun getUser(): User
}
class UserRepository(private val retrofitService: UserApi) {
suspend fun getUser() : User {
val user = retrofitService.getUser()
...
return user
}
}
然后可以在ViewModelScope中对Repository进行调用
viewModelScope.launch {
val user = userRepository.getUser()
...
}
Repository请求过程中的异常,需要通过try...catch
进行捕获
viewModelScope.launch {
try {
val user = userRepository.getUser()
...
} catch (e: Exception) {
...
}
}
此外,也可以使用runCatching
,这种方式使用起来更加优雅
viewModelScope.launch {
runCatching { userRepository.getUser() }
.onSuccess { ... }
.onFailure { ... }
}
另外,也可以在Repository中捕获异常,然后通过包装类返回结果,如下:
class UserRepository(private val retrofitService: UserApi) {
suspend fun getUser() : NetWorkResult<User> {
try {
val user = retrofitService.getUser()
return NetWorkResult.Success(user)
} catch (e: Exception) {
return NetWorkResult.Error(e)
}
}
}
LiveData KTK
提供了LiveData的Builder方式,我们可以将ViewModelScope中的实现写到builder中,调用emit
为liveData赋值,让代码达到极致的简洁(参考: 使用LiveData Builder让代码更简洁)
val user = liveData<User> {
runCatching { repository.getUser() }
.onSuccess { emit(it) }
.onFailure { ... }
...
}
Flow
可以在Coroutine中处理stream式数据,相当于RxJava的Observable
或者Flowable
。
我们可以在init初始化块中对Flow进行订阅,可以避免造成重复订阅。同时在init中也可以通过SavedStateHandle
进行状态初始化
class MyViewModel(private val state: SavedStateHandle) : ViewModel() {
init {
...
}
}
触发订阅可以使用collect
、collectLatest
或者launchIn
class MyViewModel(...) : ViewModel() {
init {
viewModelScope.launch {
repository.getFlowStream()
.collect { ... }
}
repository.getFlowStream()
.onEach { ... }
.launchIn(viewModelScope)
}
}
通过catch
和onCompletion
处理异常以及结束通知
repository.getFlowStream()
.onEach { ... }
.catch { ... }
.onCompletion { ... }
.launchIn(viewModelScope)
个人推荐使用launchIn
,写法上更加声明式、缩进更少
viewModelScope
可以在必要的时候对Coroutine自动进行后处理try...catch
,runCatching
的写法上更优雅launchIn
可以让Flow
的订阅更加声明式