Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accessing graph-scoped ViewModel of child NavHostFragment using by navGraphViewModels

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.

like image 993
Sean Blahovici Avatar asked Aug 31 '19 19:08

Sean Blahovici


People also ask

What is navGraphViewModels?

navGraphViewModels is a kotlin extension function for fragments that creates nav graph scoped view models. It accepts two parameters: a nav graph ID and also a viewmodel factory. You can simply write: Val order selection by navGraphViewModels(R. id.

What is NavController in Android?

NavController manages app navigation within a NavHost . Apps will generally obtain a controller directly from a host, or by using one of the utility methods on the Navigation class rather than create a controller directly. Navigation flows and destinations are determined by the navigation graph owned by the controller.

How do I use NavHostFragment?

To add the NavHostFragment, you have to go to your . xml file of your activity and add the following. The app:defaultNavHost=”true” is simply stating that you want this to be the NavHost that intercepts and works as the back button on your device.


2 Answers

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)
like image 111
Seanghay Avatar answered Sep 18 '22 18:09

Seanghay


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)

like image 36
Igor Avatar answered Sep 20 '22 18:09

Igor