Communication of Fragments in the Fragment, using Navigation Component, MVVM and Koin

Piotr Prus
Schibsted Tech Polska
4 min readApr 22, 2020
Photo by Jordan Madrid on Unsplash

Good in-app navigation is essential for the user experience. As is the flexibility that allows you to program to the new requirements. By introducing Navigation Component in Android Jetpack, Google showed a straight way to maintain the flow of our app from a central location.

In this article, I will show you how to use nested fragments in Navigation with additional help from Koin to provide the same instance of ViewModel across different destinations.

The setup

We are going to follow the single activity navigation pattern recommended by Google (link). For this activity, let’s set 3 different fragments and name them One, Two and Three.

Fragment Three will hold additionally 2 other fragments named X and Y.
Just like on the sketch below:

Navigation Host

Navigation used in the navigation component is done with Nav Host Fragment.

NavHostFragment provides an area within your layout for self-contained navigation to occur.

This means the above schema is not entirely correct. We are missing very important pieces, which are NavHostFragments. NavHost can be attached to Activity or Fragment and since it is a Fragment container, it has its lifecycle.

More about that in the next part of the article. Let’s see the updated schema for our navigation:

Navigation Host Scope

As mentioned earlier, the NavHost is a fragment container and hence, it has its lifecycle. The creation of NavHostFragment requires a view, so the lifecycle functions where NavHostFragment can be created or called are as follows:

  • onCreate() for an Activity
  • onActivityCreated() for Fragments

Let’s have a look at some code.

Activity layout file with navHostFragment attached

We do not need to specify any additional information in Activity class since this NavHostFragment(with associated NavController) is the default one, whenever we want to call it, we can use: findNavController(). The Navigation Component Library will check if any nav controller is associated with this view. If there is no NavHostFragment to which we could refer, the IllegalStateException will be thrown.

The creation looks a bit different for a fragment that is already a part of Navigation within the default(parent) NavController. We need to specify explicitly which fragment will be used as HostFragment. Apart from that, we also need to add the <fragment/> to the view. This time, we set the variable: defaultNavHost to false.

As you can see on the above snippet, to find the proper fragment, we are using childFragmentManager.

Now, whenever we would like to use it in fragments X or Y, we will just call a findNavController(). This will return navController associated with nested NavHostFragment(created in fragment Three). It works, because fragments X and Y are created within Fragment Three. Those, cannot refer to the main navController, cause it operates in different scope. Ok, so how to use it? Let me explain in the next paragraph.

Beyond the Scope

We have already determined that the nested fragment(child) cannot directly refer to the activity navHostFragment. Which Fragment can? All the Fragments that are included in the main navigation graph(navHostFragment). To avoid confusion I marked those with a red rectangle.

We are interested in fragment Three, which is part of the main navigation and a starter point of nested navigation. We will call requireParentFragment from the fragment X to access fragment Three. RequireParentFragment returns the parent Fragment containing this Fragment.

val fragmentThree = requireParentFragment().requireParentFragment() as ThreeFragment

As you can see, we are calling it twice, because NavHostFragment is a fragment too. This is essential for providing one ViewModel for all child fragments that are in the scope of parent fragment as well.

One (ViewModel) to rule them all

Consider the following use case: two child fragments (as shown in the example above) communicate with each other to display some data and a parent fragment that manages their visibility based on this data. Using Google's recommended architecture, we can use ViewModel for app logic. This raises a problem of the definition of the scope for that ViewModel.

At first, I thought it would not be difficult. All you have to do is initialize the ViewModel in the scope of NavHost Fragment. Unfortunately, this is not the case. To share an instance of ViewModel for parent and child fragments, we need to set the scope to parent fragment (Three), not NavHost, cause Three is a parent for the NavHost. How to do it?

  • Using ViewModelProvider

Disclaimer: ViewModelProviders is currently deprecated, as for fragment:2.2.0

We can use the constructor for ViewModelProvider() and pass ViewModelStore as a parameter. ViewModelStore is an object that manages the current state of ViewModel. More about it, here.

ViewModelProvider(requireParentFragment().requireParentFragment())
.get(ParentViewModel::class.java)
  • Using by viewmodels()

Property delegate from the library: androidx.fragment:fragment-ktx:1.2.4. It creates ViewModel lazy and scopes it to this Fragment by default. The default scope may be overridden with parameter: ownerProducer.

val viewModel by viewModels<ParentViewModel>(ownerProducer = { requireParentFragment().requireParentFragment() })

This is also a property delegate that provides the ViewModel lazily. The default scope may be overridden with parameter: from.

val viewModel: ParentViewModel by sharedViewModel(from = { requireParentFragment().requireParentFragment() })

That’s all. If you want to use the same instance of viewModel in a few fragments, define the scope properly and you are ready to go.

--

--

Piotr Prus
Schibsted Tech Polska

Android Developer @Tilt, Enthusiast of kotlin, jetpack compose and clean architecture. Currently Composing and KMMing all the things ❤️