ViewModel保存状态的方式(六)

一、前言

虽然ViewModel已经帮我们处理了屏幕旋转导致的页面销毁重建导致的数据保留问题,但是对于由于内存不足等原因导致得页面销毁重建并没有做到数据保存。本篇主要根据官方文档和示例进行一个统一的完善和整理,辅以代码示例进行演示。

二、添加依赖

implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.4.0"

三、SavedStateHandle

ViewModel中主要是使用SavedStateHandle进行数据保存,这里对这个使用进行下简单的演示。

定义如下:

class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() { ... }
class MainFragment : Fragment() {
    val vm: SavedStateViewModel by viewModels()

    ...
}

保存和取值如下:

class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
    val filteredData: LiveData<List<String>> =
        savedStateHandle.getLiveData<String>("query").switchMap { query ->
        repository.getFilteredData(query)
    }

    fun setQuery(query: String) {
        savedStateHandle["query"] = query
    }
}

通过使用 SavedStateHandle,查询值会在进程终止后保留下来,从而确保用户在重新创建前后看到同一组过滤后的数据,而无需 Activity 或 Fragment 手动保存、恢复该值并将其重新转给 ViewModel

此外,SavedStateHandle 还包含其他您与键值对映射互动时可能会用到的方法:

四、支持类型

默认情况下,您可以对 SavedStateHandle 调用 set()get(),以处理与 Bundle 相同的数据类型,如下所示。

类型/类支持数组支持
doubledouble[]
intint[]
longlong[]
StringString[]
bytebyte[]
charchar[]
CharSequenceCharSequence[]
floatfloat[]
ParcelableParcelable[]
SerializableSerializable[]
shortshort[]
SparseArray
Binder
Bundle
ArrayList
Size (only in API 21+)
SizeF (only in API 21+)

如果该类没有扩展上述列表中的任何一项,则应考虑通过添加 @Parcelize Kotlin 注解或直接实现 Parcelable 来使该类变为 Parcelable 类型。

五、保存非 Parcelable 类

如果某个类未实现 ParcelableSerializable 且不能修改为实现这些接口之一,则无法直接将该类的实例保存到 SavedStateHandle 中。

Lifecycle 2.3.0-alpha03 开始,SavedStateHandle 允许您保存任何对象,具体方法是:使用 setSavedStateProvider() 方法提供您自己的逻辑用于将对象作为 Bundle 来保存和恢复。SavedStateRegistry.SavedStateProvider 是一个接口,用于定义单个 saveState() 方法来返回包含您希望保存的状态的 Bundle。当 SavedStateHandle 准备好保存其状态后,它会调用 saveState() 以从 SavedStateProvider 检索 Bundle,并为关联的键保存 Bundle

设想一个示例应用,该应用通过 ACTION_IMAGE_CAPTURE Intent 向相机应用请求图片,并传递一个临时文件,以供相机存储图片。TempFileViewModel 封装了创建该临时文件的逻辑。

为确保临时文件在 Activity 的进程终止随后又恢复后不会丢失,TempFileViewModel 可以使用 SavedStateHandle 保留其数据。如需允许 TempFileViewModel 保存其数据,请实现 SavedStateProvider,并在 ViewModelSavedStateHandle 上将其设置为提供程序:

如需在用户返回时恢复 File 数据,请从 SavedStateHandle 中检索 temp_file Bundle。这正是 saveTempFile() 提供的包含绝对路径的 Bundle。该绝对路径随后可用于实例化新的 File

其参考代码如下:

//保存状态
//异常保存状态
private fun File.saveTempFile() = bundleOf("path" to absolutePath)
class SaveStateViewModel(private val state: SavedStateHandle): ViewModel() {
    private var tempFile: File? = null
    init {
        state.setSavedStateProvider("temp_file") { // saveState()
            tempFile?.saveTempFile() ?: Bundle()
        }
    }

    fun createOrGetTempFile(): File {
        return tempFile ?: File.createTempFile("temp", null).also {
            tempFile = it
        }
    }
}

六、AbstractSavedStateViewModelFactory

前例看到可以使用viewModels()构建ViewModel,而且无需我们传入相应的SavedStateHandle实例,但是这种构造函数也不能传递多余的参数,因为我们在使用Factory的方式创建新的ViewModel实例的时候需要采用新的方式,示例如下:

class DetailViewModel(
    private val githubApi: GithubApi,
    private val handle: SavedStateHandle
) : ViewModel() {
    fun loadData() {
        val id = handle["id"] ?: "default"
        viewModelScope.launch {
            val response = githubApi.getCommit(id)
            // Handle response
        }
    }
}
class DetailViewModelFactory(
    private val githubApi: GithubApi,
    owner: SavedStateRegistryOwner,
    defaultArgs: Bundle? = null
) : AbstractSavedStateViewModelFactory(owner, defaultArgs) {
    override fun <T : ViewModel> create(
        key: String,
        modelClass: Class<T>,
        handle: SavedStateHandle
    ): T {
        return DetailViewModel(githubApi, handle) as T
    }
}

使用如下:

class DetailActivity : AppCompatActivity() {
    private val githubApi = GithubApi()

    private val viewModel by viewModels { DetailViewModelFactory(githubApi, this) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // Use viewModel here
        viewModel.loadData()
    }
}

因为ComponentActivity已经实现了SavedStateRegistryOwner接口,所以这里直接传入this既可。

七、参考链接

  1. ViewModel 的已保存状态模块

  2. 使用 ViewModel SavedState 和 Dagger 保存 UI 状态

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值