首页 > 解决方案 > 在应用进程被杀死之前,如何在 ViewModel 的 onCleared 方法中为 SavedStateHandle 提供的 LiveData 设置特定值

问题描述

在我的个人项目中,我使用 Hilt(依赖注入)、SavedStateHandle 和 ViewModel。

SavedStateHandle 允许我在进程死亡时保存数据。

因为 ViewModel 在进程死亡后无法生存,所以我使用 Hilt 注入在 ViewModel 中注入了 savedStateHandle 对象。

当使用 coroutines/viewModelScope 处理 http 请求时,我使用自定义类“ViewState”来处理从 UI 观察到的一个 LiveData 中的多个状态。

例如:LiveData<ViewState<User>> 然后在 UI 中,我绑定了 viewState:viewMvc.bind(userViewState)

sealed class ViewState<T> : Serializable {

    data class Loading<T>(@Transient val job: Job? = null, val oldResult: T? = null) : ViewState<T>(), Serializable
    data class Fetched<T>(val result: T, val fetchedDate: Date = Date()) : ViewState<T>(), Serializable
    data class Error<T>(val e: Exception, val oldResult: T? = null) : ViewState<T>(), Serializable

    override fun toString(): String {
        return when (this) {
            is Loading -> "ViewState.Loading - oldResult: $oldResult"
            is Fetched -> "ViewState.Fetched - result: $result, fetched date: $fetchedDate"
            is Error -> "ViewState.Error - class: ${e::class.java.simpleName}, message: ${e.localizedMessage?.toString()}"
        }
    }

    fun lastResult(): T? {
        return when (this) {
            is Fetched -> result
            is Loading -> oldResult
            is Error -> oldResult
        }
    }
}

在 ViewState.Loading 类中,作业是瞬态的,因为它不可序列化。而且在 viewModel 中使用 SavedStateHandle 之前,我不需要序列化 ​​LiveData 值,因为它在创建 ViewModel 时被初始化为 null。

问题是

当应用程序进程在用户的 viewState 为 ViewState.Loading 时被杀死时,viewModelScope 不再处于活动状态,并且与其关联的作业在 ViewModel 的 onCleared() 中被取消。

天真地,我认为当“onCleared”事件发生时,我必须将 LiveData 的值更改为 null 或任何我想避免 ViewState.Loading 值跨进程死亡但显然 SavedStateHandle 此时已经保存;因为否则当应用程序状态恢复时,用户视图状态是 ViewState.Loading() 并且我正在等待由于作业已经取消而永远不会发生的事情。序列化 SavedStateHandle 时,我永远不应该保存 ViewState.Loading 对象。所以在实践中“工作”变量不应该是瞬态的,因为它永远不会被序列化。

如何截取 SavedStateHandle 被序列化/打包的确切时间以避免此类问题?

标签: androidandroid-livedataandroid-viewmodelviewmodel-savedstate

解决方案


我想你不能(除非你在活动/片段中自己保存/恢复)。

但是,您可以在还原后绕过它,而不是阻止存储加载状态。例如,当您恢复的状态为正在加载时,您可以再次调用加载函数。

// Inside your viewModel
init{
    if(savedState.contains("Loading)){
        viewModelScope.launch{
            loadData()
        }
    }
}

推荐阅读