【问题标题】:How to lazily save ViewModel's SavedStateHandle?如何懒惰地保存 ViewModel 的 SavedStateHandle?
【发布时间】:2020-10-29 04:09:11
【问题描述】:

我有一个屏幕,可以加载一堆请求并在同一个屏幕和一个外部 WebView 上从用户那里收集一些数据。因此,我有一个包含这些复杂请求对象(+ 用户输入数据)的 ViewModel。我需要通过SavedStateHandle 设计的系统启动的进程死亡来保存这些数据。但我不想将这些数据保存在数据库中,因为它只与当前的用户体验相关。

我已将我的 ViewModel 与 Hilt 集成并收到了SaveStateHandle。因为我有一些复杂的对象可以在代码中的多个位置访问/修改,所以我无法“随时随地”保存它们。我让它们实现了Parcelable,只是想立即保存它们。不幸的是,ViewModel 没有像 onSaveInstanceState() 这样的生命周期方法。

现在,我尝试使用onCleared(),这听起来像是一个可以写入句柄的地方。但事实证明,我在那里执行的所有.set() 操作都会丢失(我正在使用开发人员选项“不保留活动”进行测试。当我在其他地方使用.set() 时,它确实有效)。因为 ViewModel 不绑定到单个片段/活动的生命周期,而是绑定到 NavGraph,我无法从他们的onSaveInstanceState() 调用。

我如何/在哪里可以在SaveStateHandle 中正确保存我的状态?

【问题讨论】:

    标签: android viewmodel android-savedstate viewmodel-savedstate


    【解决方案1】:

    这正是Lifecycle 2.3.0-alpha03 release 启用的用例:

    SavedStateHandle 现在支持延迟序列化,允许您为给定键调用setSavedStateProvider(),提供SavedStateProvider,当SavedStateHandle 被要求保存其状态时,该saveState() 将得到回调。 (b/155106862)

    这使您可以处理任何复杂的对象并在需要保存时准确地获得回调。

    var complexObject: ComplexObject? = null
    
    init {
        // When using setSavedStateProvider, the underlying data is
        // stored as a Bundle, so to extract any previously saved value,
        // we get it out of the Bundle, if one exists
        val initialState: Bundle = savedStateHandle.get<Bundle?>("complexObject")
        if (initialState != null) {
            // Convert the previously saved Bundle to your ComplexObject
            // Here, it is a single Parcelable, so we'll just get it out of
            // the bundle
            complexObject = initialState.getParcelable("parcelable")
        }
    
        // Now to register our callback for when to save our object,
        // we use setSavedStateProvider()
        savedStateHandle.setSavedStateProvider("complexObject") {
            // This callback requires that you return a Bundle.
            // You can either add your Parcelable directly or
            // skip being Parcelable and add the fields to the Bundle directly
            // The key is that the logic here needs to match your
            // initialState logic above.
            Bundle().apply {
                putParcelable("parcelable", complexObject)
            }
        }
    }
    

    【讨论】:

    • 我似乎在这里遇到了一个错误。当我通过意图打开 chrome 自定义选项卡时,已正确调用 savedStateProvider 并且一切正常。但是当用户使用系统“概览按钮”导航到不同的应用程序时,不会进行调用(尽管调用了我的MainActivityonSaveInstanceState)。有什么想法吗?
    • @A1m - 如果您认为存在错误,您可以 file an issue against Lifecycle 使用示例项目重现您的问题。
    • 我个人认为这个解决方案很荒谬,为什么不在 ViewModel 中有一个override fun onSaveInstanceState() 方法呢?还是只支持在 onCleared() 中保存状态?恕我直言,这是一个巨大的疏忽。
    • @Wess - 你当然可以让ViewModel 的自定义子类有一个隐藏所有这些逻辑的onSaveInstanceState() 方法,但是SavedStateHandle,实际上控制所有状态保存的类,可以从任何ViewModel 类(无论是ViewModelAndroidViewModel,还是您拥有的任何其他自定义类型层次结构)中都有目的地使用。请注意,onCleared() 永远不会是保存状态的合适位置 - 只有当您的 ViewModel 被永久销毁并且永远不会回来时才会调用它。
    • @ianhanniballake 这是有道理的。但我的建议是提供一个覆盖方法,该方法向ViewModel 发出信号,表明它很快就会进入保存状态,并且它应该在该方法中将它想要的内容保存到SavedStateHandle 中。 I.o.w.类似于Fragments 中的override fun onSaveInstanceState(outState: Bundle),除了它没有参数Bundle - 它的目的只是通知ViewModel 它现在应该保存任何状态。在我看来,这样的事情对于开发人员来说是一种更简单、更熟悉的解决方案,可以懒惰地保存状态,而不需要创建子类。
    猜你喜欢
    • 1970-01-01
    • 2023-03-29
    • 2016-01-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多