如何在不使用 NavGraph 或将其作用域限定于 Activity 的情况下,在特定一组 Fragment 之间共享 ViewModel 范围?

5

谷歌建议使用SharedViewModel在Fragment之间进行通信,并将其作用域限定为活动。

在单个Activity应用程序中,这意味着Activity将充斥着可能不再需要的ViewModel,并且它们将在整个生命周期内保留。考虑一个扩展注册流程或涉及几个屏幕的示例。

一种推荐的方法是使用父Fragment作为作用域,但如果没有父Fragment,则只能使用不同的Fragment。

我想出了以下解决方案,想知道它有多可行或有多糟糕,是否有更好的解决方法?

假设我有两个名为ExampleOneFragmentExampleTwoFragment的Fragment,为简单起见,我希望它们具有共享作用域,而不实际将其作用域限定为活动。比如说,我想从ExampleTwoFragment更新ExampleOneFragment中的文本视图,因此我为两者创建了一个SharedViewModel,如下所示:

对于ExampleOneFragment,它将是:

 private val mSharedViewModel by lazy {
    ViewModelProvider(this).get(SharedViewModel::class.java)
}

对于ExampleTwoFragment,我想出了以下内容:

private val mSharedViewModel by lazy {
    ViewModelProvider(supportFragmentManager().findFragmentByTag(ExampleOneFragment.TAG) ?: this).get(SharedViewModel::class.java)
}

这似乎是有效的,但我不知道可能会引起什么问题。

我发现了另一些解决方案: 根据@mikhehc 在这里,实际上我们可以创建自己的 ViewModelStore。这将允许我们对 ViewModel 存在的范围进行精细控制。但我不知道如何让它适用于 Fragment?

其次,是通过使用相同的键并使用虚拟viewmodel 清除活动范围内的ViewModel的hacky方法,我在这里找到了

有人能指导我正确的方法吗?我不能切换到NavGraphs,因为这已经是一个正在运行的项目,而范围限定到活动范围内感觉就不对。谢谢。


就我个人而言,当我将一个ViewModel与两个片段共享时,我遇到了onCleared()的调用。这个调用会清除我的范围和与之相关联的所有协程。尽管通过dagger注入的ViewModel被标记为@Singleton。我希望通过你的问题进一步了解这种行为。 - Gleichmut
1个回答

2
这似乎是有效的,但我不知道会引起什么问题。
只有在以下情况下,此代码才能正常工作:
- 必须先创建一个 ExampleOneFragment。 - 它必须通过 ExampleOneFragment.TAG 标签添加到与 ExampleTwoFragment 使用相同的 FragmentManager 中。
如果其中任何一项假设失败,则会出现单独的视图模型实例,因为 supportFragmentManager().findFragmentByTag(ExampleOneFragment.TAG) ?: this 将解析为 this。
但我不明白如何使其适用于片段?您可以按照答案中所示的方式使用它,或者使用任何接受 ViewModelStoreOwner 的其他内容。在此行中:
val viewModel = ViewModelProvider(myApp, viewModelFactory).get(CustomViewModel::class.java)

您将从您的 Fragment 中获取 myApp,如下:

myApp

val myApp = requireContext().application as MyApp

个人而言,我认为你指出的解决方案非常危险。 ViewModelStore 的生存期为整个进程,从不清除。你最终会在所有内容中共享你的 viewmodel,并且由该 viewmodel 完成的所有操作都会泄漏。创建自定义 ViewModelStoreOwner 的概念是好的,但你应该做一些事情来将该所有者的范围与其存储的 viewmodels 的相关生命周期联系起来。答案在最后一段试图回避这个问题; 太多开发人员会忽略这一点并遇到问题。

建议的一种方法是使用父 Fragment 作为范围,但只有在存在父 fragment 时才可能实现。它可以是完全不同的 Fragments。

你的应用程序没有以使自动的 ViewModelStoreOwner 管理 "开箱即用" 的方式编写。

最终,您需要寻找一个ViewModelStoreOwner在这两个片段之间共享。 在您的第一种解决方案中,您尝试通过使用其中一个片段的ViewModelStoreOwner来突破,只有在您能够可靠地选择哪个片段时才起作用。 在您在另一个答案中指出的解决方案中,您试图通过故意泄漏ViewModelStoreOwner来突破这一点。

根据您的情况,可能会有其他方法可供考虑。 例如,在依赖反转框架(Dagger/Hilt、Koin等)中可能有一些选项,可以构建与特定一对片段实例相关联的ViewModelStoreOwner


是的,对于第二种解决方案,我并不是真的想将其限定在应用程序生命周期内,我意识到那样做会更加错误。我的意思是是否有办法创建一个仅限于片段范围的ViewModelStoreOwner。如果有,我该如何实现它?我在哪里实现ViewModelStoreOwner接口。另外,您认为清除ViewModels的第二种方法怎么样? - che10
@che10: "我应该在哪里实现ViewModelStoreOwner接口" -- 在某个任意对象上,你需要安排使其在这两个Fragment创建时可用。你还需要安排在这对Fragment被销毁时清除ViewModels。例如,你的依赖反转框架(Dagger/Hilt、Koin等)可能有一些选项,可以设置一个与特定Fragment实例相关联的ViewModelStoreOwner - CommonsWare
@che10:“目前使用活动作用域是唯一的方法”-这可能是您最简单的解决方案。我的DI参考对以“例如”开头,因为它们是例子,而DI是我可以为您提供的最后一种“通用”解决方案。您当然可以做其他事情,但这将非常依赖于您如何创建和销毁这些片段,因此我们无法就此提供具体建议。 - CommonsWare
我的当前结构只是将片段添加到彼此之上,并将它们全部添加到后退栈中。最初我发现targetFragment.onActivityResult已被弃用,而我正在DialogFragment中使用此机制。因此,我正在寻找从DialogFragment接收结果的正确流程。SharedViewModel被推荐,但仅为此限定到活动范围并不合适。所以现在我陷入了选择何种方法的困境。我找不到任何适当替代DialogFragment的东西。 - che10
@che10:"如果有一天我更新了 Kotlin 插件但它不再支持旧项目" -- 那么你可以将 Kotlin 插件版本回滚到之前使用的版本,直到你能够进行必要的更改。 - CommonsWare
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接