如何将视图模型范围限定为父片段?
How to scope a view model to a parent fragment?
所以我正在使用新的导航组件(使用一个 activity 原则)并使用共享视图模型在每个片段之间进行通信,但是,我已经到了有时需要清除的地步视图模型,但我找不到清除它的好地方。但老实说,我认为与其自己尝试清除它,不如让框架为我做这件事,但这并不是因为视图模型是共享的,并且范围限定为 activity,但我想我可以将它们的范围限定为父片段,我画了一张图来说明我正在尝试做什么。 所以我只想在从当前视图 "Child 1 Child a" 导航回来时清除 2 个视图模型模型永远不会被清除,试图通过在片段中调用 'this' 来获取当前的视图模型,并且在子项中调用 getParentFragment 不起作用,任何人都可以提供一个例子吗?
编辑
看起来我已经在做类似的事情,但它在我的情况下不起作用,所以我将添加一些代码,这是我如何访问父片段中的第一个视图模型
model = ViewModelProviders.of(this).get(RequestViewModel.class);
然后在子片段中,我这样做
requestViewModel = ViewModelProviders.of(getParentFragment()).get(RequestViewModel.class);
但它并没有在它们之间保留数据,它们都附加了观察者
好的,所以在 parent
中使用它
model = ViewModelProviders.of(this).get(RequestViewModel.class);
这在 child
requestViewModel = ViewModelProviders.of(getParentFragment()).get(RequestViewModel.class);
给了我不同的哈希码,但 ID 相同,这似乎是因为导航组件,如果我将它们都更改为 getParentFragment 那么它就可以工作,所以我认为该组件正在替换片段而不是在此处添加它们,非常感谢@WadeWilson 和@JeelVankhede
因此,根据 @martin 的 得出即使 One/Many 片段添加为 Child 在 父片段 中,导航组件 为两个片段提供相同的 片段管理器。
这意味着即使将片段添加为父子层次结构,它们也会共享来自导航组件的相同片段管理器(可能是此库中的错误?) & 因此ViewModels
由于在子片段中对 ViewModelProvider
使用 getParentFragment()
实例时的这种困境而未共享。
因此,共享 ViewModels
的一个快速解决方案是从片段管理器获取父片段的实例,对父片段和子片段使用以下行:
ViewModelProviders.of(getParentFragment()).get(SharedViewModel.class); // using this in both parent amd child fragment would do the trick !
Google 让我们现在能够将 ViewModel 的范围限定到导航图。如果您已经在使用导航组件,则可以使用它。 (个人意见,如果你还没有使用它,你应该移动到导航组件,因为它很容易使用。从一个试图自己管理后台堆栈的人那里得到它)
您可以select所有需要在导航图中组合在一起的片段和right-click->move to nested graph->new graph
现在这会将 selected 片段移动到主导航图中的嵌套图,如下所示:
<navigation app:startDestination="@id/homeFragment" ...>
<fragment android:id="@+id/homeFragment" .../>
<fragment android:id="@+id/productListFragment" .../>
<fragment android:id="@+id/productFragment" .../>
<fragment android:id="@+id/bargainFragment" .../>
<navigation
android:id="@+id/checkout_graph"
app:startDestination="@id/cartFragment">
<fragment android:id="@+id/orderSummaryFragment".../>
<fragment android:id="@+id/addressFragment" .../>
<fragment android:id="@+id/paymentFragment" .../>
<fragment android:id="@+id/cartFragment" .../>
</navigation>
</navigation>
现在,在初始化 ViewModel 时在片段中执行此操作
val viewModel: CheckoutViewModel by navGraphViewModels(R.id.checkout_graph)
如果你需要传递视图模型工厂(可能是为了注入视图模型)你可以这样做:
val viewModel: CheckoutViewModel by navGraphViewModels(R.id.checkout_graph) { viewModelFactory }
确保它是 R.id.graph
而不是 R.navigation.graph
出于某种原因,创建导航图并使用 include
将其嵌套在主导航图中对我不起作用。可能是一个错误。
谢谢, 为我指明了正确的方向。
在您的应用中使用 Fragment-ktx libr,您可以获得如下视图模型
在您的应用程序中-> build.gradle
implementation 'androidx.fragment:fragment-ktx:1.1.0'
// 获取 ParentFragment 中的 ViewModel as
private val viewModel: DemoViewModel by viewModels()
// 在 ChildFragment 中获取与 ViewModel 相同的实例
private val viewModel: DemoViewModel by viewModels(
ownerProducer = { requireParentFragment() }
)
使用 Kotlin 和惰性初始化的版本(又称 by viewModels
)
在父片段中(例如 ViewPager2)
private val viewModel: MyViewModel by viewModels({ this }) {
TODO("MyViewModelFactory instance")
}
在子 Fragment 中(例如 ViewPager2 中的页面)
private val viewModel: MyViewModel by viewModels({ requireParentFragment() })
如果您使用的是导航组件 (https://developer.android.com/guide/navigation),
您需要以这种方式获取 viewModel:
在您的应用中实施 fragment-ktx -> build.gradle:
implementation 'androidx.fragment:fragment-ktx:1.2.5
在父片段中:
private val viewModel by viewModels<ParentFragmentViewModel>()
在子片段中
private val viewModel by viewModels<ParentFragmentViewModel>({requireGrandParentFragment()})
requireGrandParentFragment() 是 Fragment 的自定义扩展:
fun Fragment.requireGrandParentFragment() = this.requireParentFragment().requireParentFragment()
您需要向上两层访问 parentFragment 的 viewModel 的原因是因为导航组件上 childFragment 的第一个父级是 NavHostFragment 和 NavHostFragment 是 viewModel 所在的 parentFragment。
如果您没有使用导航组件,您可以在 childFragment 中像这样访问它:
private val viewModel by viewModels<ParentFragmentViewModel>({requireParentFragment()})
我在所有解决方案中都遇到了同样的问题,但在我的场景中没有工作想出另一个解决方案,使用 requireParentFragment() return NavHostFragment in child fragment 通过使用
解决它
private val viewModel: childViewModel by viewModels(
ownerProducer = { requireParentFragment().childFragmentManager.primaryNavigationFragment!! }
)
在Parent Fragmet中使用这个
private val viewModel: MyOrdersVM by viewModels()
我做错的是向 DialogFragment
提供了错误的片段管理器。
所以在我的例子中它是这样工作的:
class MyDialog : DialogFragment() {
private val viewModel: MyViewModel by viewModels({ requireParentFragment() })
并在我的 fragment
中初始化对话框:
private fun showDialog(){
MyDialog().show(childFragmentManager, "AddFriendToGroupDialog")
}
我正在使用 implementation 'androidx.fragment:fragment-ktx:1.3.0'
和导航图。
我也有这个问题。为子片段创建了一个新的 ViewModel。
问题是 getParentFragment() returns NavHostFragment 而不是所需的片段。
并且我们需要在子片段中获取真正的父对象。
如果我们这样做 requireParentFragment().requireParentFragment(),那么我们就会得到真正的父级。
解决方法:(子更新)
Fragment parent = requireParentFragment().requireParentFragment();
viewModel = new ViewModelProvider(parent).get(RequestViewModel.class);
Found a solution here
所以我正在使用新的导航组件(使用一个 activity 原则)并使用共享视图模型在每个片段之间进行通信,但是,我已经到了有时需要清除的地步视图模型,但我找不到清除它的好地方。但老实说,我认为与其自己尝试清除它,不如让框架为我做这件事,但这并不是因为视图模型是共享的,并且范围限定为 activity,但我想我可以将它们的范围限定为父片段,我画了一张图来说明我正在尝试做什么。
编辑
看起来我已经在做类似的事情,但它在我的情况下不起作用,所以我将添加一些代码,这是我如何访问父片段中的第一个视图模型
model = ViewModelProviders.of(this).get(RequestViewModel.class);
然后在子片段中,我这样做
requestViewModel = ViewModelProviders.of(getParentFragment()).get(RequestViewModel.class);
但它并没有在它们之间保留数据,它们都附加了观察者
好的,所以在 parent
中使用它model = ViewModelProviders.of(this).get(RequestViewModel.class);
这在 child
requestViewModel = ViewModelProviders.of(getParentFragment()).get(RequestViewModel.class);
给了我不同的哈希码,但 ID 相同,这似乎是因为导航组件,如果我将它们都更改为 getParentFragment 那么它就可以工作,所以我认为该组件正在替换片段而不是在此处添加它们,非常感谢@WadeWilson 和@JeelVankhede
因此,根据 @martin 的
这意味着即使将片段添加为父子层次结构,它们也会共享来自导航组件的相同片段管理器(可能是此库中的错误?) & 因此ViewModels
由于在子片段中对 ViewModelProvider
使用 getParentFragment()
实例时的这种困境而未共享。
因此,共享 ViewModels
的一个快速解决方案是从片段管理器获取父片段的实例,对父片段和子片段使用以下行:
ViewModelProviders.of(getParentFragment()).get(SharedViewModel.class); // using this in both parent amd child fragment would do the trick !
Google 让我们现在能够将 ViewModel 的范围限定到导航图。如果您已经在使用导航组件,则可以使用它。 (个人意见,如果你还没有使用它,你应该移动到导航组件,因为它很容易使用。从一个试图自己管理后台堆栈的人那里得到它)
您可以select所有需要在导航图中组合在一起的片段和right-click->move to nested graph->new graph
现在这会将 selected 片段移动到主导航图中的嵌套图,如下所示:
<navigation app:startDestination="@id/homeFragment" ...>
<fragment android:id="@+id/homeFragment" .../>
<fragment android:id="@+id/productListFragment" .../>
<fragment android:id="@+id/productFragment" .../>
<fragment android:id="@+id/bargainFragment" .../>
<navigation
android:id="@+id/checkout_graph"
app:startDestination="@id/cartFragment">
<fragment android:id="@+id/orderSummaryFragment".../>
<fragment android:id="@+id/addressFragment" .../>
<fragment android:id="@+id/paymentFragment" .../>
<fragment android:id="@+id/cartFragment" .../>
</navigation>
</navigation>
现在,在初始化 ViewModel 时在片段中执行此操作
val viewModel: CheckoutViewModel by navGraphViewModels(R.id.checkout_graph)
如果你需要传递视图模型工厂(可能是为了注入视图模型)你可以这样做:
val viewModel: CheckoutViewModel by navGraphViewModels(R.id.checkout_graph) { viewModelFactory }
确保它是 R.id.graph
而不是 R.navigation.graph
出于某种原因,创建导航图并使用 include
将其嵌套在主导航图中对我不起作用。可能是一个错误。
谢谢,
在您的应用中使用 Fragment-ktx libr,您可以获得如下视图模型 在您的应用程序中-> build.gradle
implementation 'androidx.fragment:fragment-ktx:1.1.0'
// 获取 ParentFragment 中的 ViewModel as
private val viewModel: DemoViewModel by viewModels()
// 在 ChildFragment 中获取与 ViewModel 相同的实例
private val viewModel: DemoViewModel by viewModels(
ownerProducer = { requireParentFragment() }
)
使用 Kotlin 和惰性初始化的版本(又称 by viewModels
)
在父片段中(例如 ViewPager2)
private val viewModel: MyViewModel by viewModels({ this }) {
TODO("MyViewModelFactory instance")
}
在子 Fragment 中(例如 ViewPager2 中的页面)
private val viewModel: MyViewModel by viewModels({ requireParentFragment() })
如果您使用的是导航组件 (https://developer.android.com/guide/navigation), 您需要以这种方式获取 viewModel:
在您的应用中实施 fragment-ktx -> build.gradle:
implementation 'androidx.fragment:fragment-ktx:1.2.5
在父片段中:
private val viewModel by viewModels<ParentFragmentViewModel>()
在子片段中
private val viewModel by viewModels<ParentFragmentViewModel>({requireGrandParentFragment()})
requireGrandParentFragment() 是 Fragment 的自定义扩展:
fun Fragment.requireGrandParentFragment() = this.requireParentFragment().requireParentFragment()
您需要向上两层访问 parentFragment 的 viewModel 的原因是因为导航组件上 childFragment 的第一个父级是 NavHostFragment 和 NavHostFragment 是 viewModel 所在的 parentFragment。
如果您没有使用导航组件,您可以在 childFragment 中像这样访问它:
private val viewModel by viewModels<ParentFragmentViewModel>({requireParentFragment()})
我在所有解决方案中都遇到了同样的问题,但在我的场景中没有工作想出另一个解决方案,使用 requireParentFragment() return NavHostFragment in child fragment 通过使用
解决它private val viewModel: childViewModel by viewModels(
ownerProducer = { requireParentFragment().childFragmentManager.primaryNavigationFragment!! }
)
在Parent Fragmet中使用这个
private val viewModel: MyOrdersVM by viewModels()
我做错的是向 DialogFragment
提供了错误的片段管理器。
所以在我的例子中它是这样工作的:
class MyDialog : DialogFragment() {
private val viewModel: MyViewModel by viewModels({ requireParentFragment() })
并在我的 fragment
中初始化对话框:
private fun showDialog(){
MyDialog().show(childFragmentManager, "AddFriendToGroupDialog")
}
我正在使用 implementation 'androidx.fragment:fragment-ktx:1.3.0'
和导航图。
我也有这个问题。为子片段创建了一个新的 ViewModel。 问题是 getParentFragment() returns NavHostFragment 而不是所需的片段。
并且我们需要在子片段中获取真正的父对象。
如果我们这样做 requireParentFragment().requireParentFragment(),那么我们就会得到真正的父级。
解决方法:(子更新)
Fragment parent = requireParentFragment().requireParentFragment();
viewModel = new ViewModelProvider(parent).get(RequestViewModel.class);
Found a solution here