Accessing graph-scoped ViewModel of child NavHostFragment using by navGraphViewModels
Asked Answered
F

3

13

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:

enter image description here

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.

Finder answered 31/8, 2019 at 19:18 Comment(0)
S
11

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)

Strawser answered 1/4, 2020 at 14:39 Comment(0)
G
6

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)
Guarnerius answered 4/9, 2019 at 10:1 Comment(0)
M
4

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 }
Messalina answered 21/5, 2020 at 7:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.