【Android】ViewModel KTX对Coroutine的支持

androidx.lifecycle:lifecycle-viewmodel-ktx是ViewModel的KTX扩展库,可以让我们在ViewModel中方便的使用Coroutine。对于ViewModel-KTX的使用,我们需要关注以下三个知识点:

  1. ViewModelScope的使用
  2. suspend函数的调用
  3. Flow的订阅

接下来通过一个MVVM架构的示例,围绕上面这三点进行一个简单介绍。
我们会在Model层使用Repository进行管理,在ViewModel层通过LiveData驱动UI更新。

androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0-rc03
Coroutine:1.3.3


1. ViewModelScope的使用


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


2. suspend函数调用


通过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的Builder方式

LiveData KTK提供了LiveData的Builder方式,我们可以将ViewModelScope中的实现写到builder中,调用emit为liveData赋值,让代码达到极致的简洁(参考: 使用LiveData Builder让代码更简洁

val user = liveData<User> {
    runCatching { repository.getUser() }
        .onSuccess { emit(it) }
        .onFailure { ... }

    ...
}

3. Flow的订阅


Flow可以在Coroutine中处理stream式数据,相当于RxJava的Observable或者Flowable
我们可以在init初始化块中对Flow进行订阅,可以避免造成重复订阅。同时在init中也可以通过SavedStateHandle进行状态初始化

class MyViewModel(private val state: SavedStateHandle) : ViewModel() {
    init {
        ...
    }
}

触发订阅可以使用collectcollectLatest或者launchIn

class MyViewModel(...) : ViewModel() {
    init {
        viewModelScope.launch {
            repository.getFlowStream()
                .collect { ... }
        }

        repository.getFlowStream()
            .onEach { ... }
            .launchIn(viewModelScope)
    }
}

通过catchonCompletion处理异常以及结束通知

repository.getFlowStream()
    .onEach { ... }
    .catch { ... }
    .onCompletion { ... }
    .launchIn(viewModelScope)

个人推荐使用launchIn,写法上更加声明式、缩进更少


总结


  • 使用viewModelScope可以在必要的时候对Coroutine自动进行后处理
  • 异常捕获需要使用try...catchrunCatching的写法上更优雅
  • 使用launchIn可以让Flow的订阅更加声明式
发布了116 篇原创文章 · 获赞 15 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/vitaviva/article/details/104866968