Reputation: 5750
I am using the Navigation Component of Android Jetpack (2.2.0-alpha01).
I wish to use a child NavHostFragment nested inside my main NavHostFragment, equipped with its own child nav graph. Please view the following image for context:
The child nav host is defined like this inside the fragment that is at the front of the MainNavHost's stack:
<fragment
android:id="@+id/childNavHostFragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="false"
app:navGraph="@navigation/child_graph" />
Inside the fragment that is at the front of the CHILD Nav Host Fragment, I am trying to get a ViewModel scoped to the R.navigation.child_graph by using the following code:
private val childGraphScopedViewModel: ChildGraphScopedViewModel by navGraphViewModels(R.navigation.child_graph) {
viewModelFactory
}
When accessing the childGraphScopedViewModel, I am getting a crash with the error message:
java.lang.IllegalArgumentException: No NavGraph with ID 2131689472 is on the NavController's back stack.
I believe the lazy init call by navGraphViewModel()
is looking for the navgraph inside the mainGraph.
How can I access a child navHostFragment scoped ViewModel? Thank you for your time.
Upvotes: 13
Views: 8647
Reputation: 3608
For some reason creating the nav graph and using include
to nest it inside the main nav graph was not working for me. Probably it is a bug.
You can select all the fragments that need to be grouped together inside the nav graph and right-click->move to nested graph->new graph
now this will move the selected fragments to a nested graph inside the main nav graph like this:
<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>
Now, inside the fragments when you initialize the ViewModel do this
val viewModel: CheckoutViewModel by navGraphViewModels(R.id.checkout_graph)
If you need to pass the viewmodel factory(may be for injecting the viewmodel) you can do it like this:
val viewModel: CheckoutViewModel by navGraphViewModels(R.id.checkout_graph) { viewModelFactory }
Upvotes: 4
Reputation: 2089
This works for me without defining aby custom ViewModelProvider
(2.2.0):
private val viewModel: ChildGraphScopedViewModel by navGraphViewModels(R.id. child_graph)
An Easy mistake to make is to use R.navigation.child_graph
(bad) instead of R.id.child_graph
(good)
Upvotes: 11
Reputation: 1086
You can do that by providing the viewModelStore of child NavController
override fun onViewCreated(
view: View,
savedInstanceState: Bundle?
) {
super.onViewCreated(view, savedInstanceState)
val childHostFragment = childFragmentManager
.findFragmentById(R.id.childNavHostFragment) as NavHostFragment
val childNavController = childHostFragment.navController
val childViewModel: ChildGraphScopedViewModel = ViewModelProvider(
childNavController.getViewModelStoreOwner(R.navigation.child_graph)
).get(ChildGraphScopedViewModel::class.java)
}
I wrote a Kotlin Extension for making it easier
inline fun <reified T: ViewModel> NavController.viewModel(@NavigationRes navGraphId: Int): T {
val storeOwner = getViewModelStoreOwner(navGraphId)
return ViewModelProvider(storeOwner)[T::class.java]
}
Usage
val viewModel = findNavController().viewModel(R.navigation.nav)
Upvotes: 6