使用导航组件将视图模型限定为多个片段(不是 activity)
Scoping a viewmodel to multiple fragments (not activity) using the navigation component
我正在使用导航组件,我希望在几个片段之间共享一个视图模型,但是当我离开片段时应该清除它们(因此不会将它们限定在 activity 范围内)我'我正在尝试采用一个 activity 多个片段的方法。我已经设法使用多个导航主机并使用 getParentFragment 将片段限定到它来实现这一点,但这只会导致更多问题必须将片段包装在其他父片段中,失去无缝工作的后退按钮和其他黑客来获得应该工作的东西很简单。有没有人知道如何实现这一目标?我想知道 getViewModelStore 是否有任何我可以使用的东西,鉴于下图,我想将视图模型的范围限定为 createCardFragment2 并在它之后的任何内容中使用它(addPredictions、editImageFragment 和其他我尚未添加的),但是如果我导航回 mainFragment 我想清除视图模型。
顺便说一句,我不能只在 mainFragment 视图模型存储上调用 clear,因为这里还有其他不应清除的视图模型,我想我想要一种方法来告诉导航主机父片段应该是什么,我'如果我从 mainFragment 或 cardPreviewFragment
导航,我知道这不会是一件事,也不是一种使视图模型成为新视图模型的方法
是的,现在可以将视图模型的范围限定为以 androidx.navigation:*:2.1.0-alpha02
开头的导航图。请参阅发行说明 here and an example of the API here。您只需要为您的导航图输入 R.id
。不过,我发现使用起来有点烦人,因为通常视图模型是在 onCreate
中初始化的,这在这个范围内是不可能的,因为导航控制器还不能保证由你的导航主机片段设置(我我发现配置更改就是这种情况。
此外,如果您不希望 mainFragment
成为该范围的一部分,我建议您将其取出并使用 nested nav graph。
这是 Alex H 接受的答案的具体示例。
在您的 build.gradle(应用程序)
dependencies {
def nav_version = "2.1.0"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
}
视图模型示例
class MyViewModel : ViewModel() {
val name: MutableLiveData<String> = MutableLiveData()
}
在你的FirstFlowFragment.kt中定义
val myViewModel: MyViewModel by navGraphViewModels(R.id.your_nested_nav_id)
myViewModel.name.value = "Cool Name"
并在你的SecondFlowFragment.kt中定义
val myViewModel: MyViewModel by navGraphViewModels(R.id.your_nested_nav_id)
val name = myViewModel.name.value.orEmpty()
Log.d("tag", "welcome $name!")
现在 ViewModel 被限定在这个嵌套片段中,当嵌套导航被销毁时共享状态也会被销毁,不需要手动重置它们。
所以当我 posted 这个功能是存在的,但没有像预期的那样工作,从那时起我现在一直在使用它并且这个问题越来越受到关注所以我想 post 一个最新的例子,
使用
//Navigation
implementation "androidx.navigation:navigation-fragment:2.2.0-rc04"
// Navigation UI
implementation "androidx.navigation:navigation-ui:2.2.0-rc04"
我得到了这样的视图模型店主
private ViewModelStoreOwner getStoreOwner() {
NavController navController = Navigation
.findNavController(requireActivity(), R.id.root_navigator_fragment);
return navController.getViewModelStoreOwner(R.id.root_navigator);
}
我使用一个 activity 多片段实现,但使用它我可以有效地将我的视图模型绑定到范围内的片段,并且使用新的 live data 你甚至可以限制它
第一个 id 来自导航图片段
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<fragment
android:id="@+id/root_navigator_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="true"
app:navGraph="@navigation/root_navigator"/>
</FrameLayout>
第二个来自导航图的id
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root_navigator"
app:startDestination="@id/mainNavFragment">
然后就可以这样使用了
private void setUpSearchViewModel() {
searchViewModel = new ViewModelProvider(getStoreOwner()).get(SearchViewModel.class);
}
因此,根据此处的答案,我创建了一个 returns 范围为当前导航图的 ViewModel
函数。
private val scopedViewModel by lazy { getNavScopedViewModel(arg) }
/**
* The [navGraphViewModels] function is not entirely lazy, as we need to pass the graph id
* immediately, but we cannot call [findNavController] to get the graph id from, before the
* Fragment's [onCreate] has been called. That's why we wrap the call in a function and call it lazily.
*/
fun getNavScopedViewModel(arg: SomeArg): ScopedViewModel {
// The id of the parent graph. If you're currently in a destination within this graph
// it will always return the same id
val parentGraphScopeId = findNavController().currentDestination?.parent?.id
?: throw IllegalStateException("Navigation controller should already be initialized.")
val viewModel by navGraphViewModels<ScopedViewModel>(parentGraphScopeId) {
ScopedViewModelFactory(args)
}
return viewModel
}
这不是最漂亮的实现,但它完成了工作
我正在使用导航组件,我希望在几个片段之间共享一个视图模型,但是当我离开片段时应该清除它们(因此不会将它们限定在 activity 范围内)我'我正在尝试采用一个 activity 多个片段的方法。我已经设法使用多个导航主机并使用 getParentFragment 将片段限定到它来实现这一点,但这只会导致更多问题必须将片段包装在其他父片段中,失去无缝工作的后退按钮和其他黑客来获得应该工作的东西很简单。有没有人知道如何实现这一目标?我想知道 getViewModelStore 是否有任何我可以使用的东西,鉴于下图,我想将视图模型的范围限定为 createCardFragment2 并在它之后的任何内容中使用它(addPredictions、editImageFragment 和其他我尚未添加的),但是如果我导航回 mainFragment 我想清除视图模型。
顺便说一句,我不能只在 mainFragment 视图模型存储上调用 clear,因为这里还有其他不应清除的视图模型,我想我想要一种方法来告诉导航主机父片段应该是什么,我'如果我从 mainFragment 或 cardPreviewFragment
导航,我知道这不会是一件事,也不是一种使视图模型成为新视图模型的方法是的,现在可以将视图模型的范围限定为以 androidx.navigation:*:2.1.0-alpha02
开头的导航图。请参阅发行说明 here and an example of the API here。您只需要为您的导航图输入 R.id
。不过,我发现使用起来有点烦人,因为通常视图模型是在 onCreate
中初始化的,这在这个范围内是不可能的,因为导航控制器还不能保证由你的导航主机片段设置(我我发现配置更改就是这种情况。
此外,如果您不希望 mainFragment
成为该范围的一部分,我建议您将其取出并使用 nested nav graph。
这是 Alex H 接受的答案的具体示例。
在您的 build.gradle(应用程序)
dependencies {
def nav_version = "2.1.0"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
}
视图模型示例
class MyViewModel : ViewModel() {
val name: MutableLiveData<String> = MutableLiveData()
}
在你的FirstFlowFragment.kt中定义
val myViewModel: MyViewModel by navGraphViewModels(R.id.your_nested_nav_id)
myViewModel.name.value = "Cool Name"
并在你的SecondFlowFragment.kt中定义
val myViewModel: MyViewModel by navGraphViewModels(R.id.your_nested_nav_id)
val name = myViewModel.name.value.orEmpty()
Log.d("tag", "welcome $name!")
现在 ViewModel 被限定在这个嵌套片段中,当嵌套导航被销毁时共享状态也会被销毁,不需要手动重置它们。
所以当我 posted 这个功能是存在的,但没有像预期的那样工作,从那时起我现在一直在使用它并且这个问题越来越受到关注所以我想 post 一个最新的例子,
使用
//Navigation
implementation "androidx.navigation:navigation-fragment:2.2.0-rc04"
// Navigation UI
implementation "androidx.navigation:navigation-ui:2.2.0-rc04"
我得到了这样的视图模型店主
private ViewModelStoreOwner getStoreOwner() {
NavController navController = Navigation
.findNavController(requireActivity(), R.id.root_navigator_fragment);
return navController.getViewModelStoreOwner(R.id.root_navigator);
}
我使用一个 activity 多片段实现,但使用它我可以有效地将我的视图模型绑定到范围内的片段,并且使用新的 live data 你甚至可以限制它
第一个 id 来自导航图片段
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<fragment
android:id="@+id/root_navigator_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="true"
app:navGraph="@navigation/root_navigator"/>
</FrameLayout>
第二个来自导航图的id
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root_navigator"
app:startDestination="@id/mainNavFragment">
然后就可以这样使用了
private void setUpSearchViewModel() {
searchViewModel = new ViewModelProvider(getStoreOwner()).get(SearchViewModel.class);
}
因此,根据此处的答案,我创建了一个 returns 范围为当前导航图的 ViewModel
函数。
private val scopedViewModel by lazy { getNavScopedViewModel(arg) }
/**
* The [navGraphViewModels] function is not entirely lazy, as we need to pass the graph id
* immediately, but we cannot call [findNavController] to get the graph id from, before the
* Fragment's [onCreate] has been called. That's why we wrap the call in a function and call it lazily.
*/
fun getNavScopedViewModel(arg: SomeArg): ScopedViewModel {
// The id of the parent graph. If you're currently in a destination within this graph
// it will always return the same id
val parentGraphScopeId = findNavController().currentDestination?.parent?.id
?: throw IllegalStateException("Navigation controller should already be initialized.")
val viewModel by navGraphViewModels<ScopedViewModel>(parentGraphScopeId) {
ScopedViewModelFactory(args)
}
return viewModel
}
这不是最漂亮的实现,但它完成了工作