Jetpack Compose Navigation and Dagger 2 with lazy initialization of modules.

Mikhail Zhalskiy
4 min readJul 13, 2023

--

Hi everyone! My name is Mikhail, I am Head of Android Division in Umbrella IT. The purpose of the article is to show how to combine Jetpack Compose Navigation and Dagger 2 with lazy initialization of modules.

Before reading, you should read the article Modularization of Android Applications with lazy initialization.

What goals do we want to achieve:

  • UI feature-component is initialized at the moment when the feature screen is opened, if there are no other dependencies on this component
  • UI feature-component exists as long as the screen exists, if there are no other dependencies on this component

Let’s make the project with the following modules and dependencies, implementing lazy initialization of components:

Let’s figure out how to initialize the component after navigating to the screen.

Goals:

  • Inject FeatureStarter into the calling feature as a dependency
  • Initialize component with FeatureStarter, only after navigation to screen

All features UI provide FeatureStarter via their api

In the app module, we will create a NavigationComponent (Dagger component), which will be responsible for providing various FeatureStarters in features. Each feature will have its own FeatureStarterProxy that implements the corresponding FeatureStarter. Such proxying will allow you to provide a FeatureStarter without initializing a component of this feature until you navigate to its screen.

Let’s change dependencies of the component: now instead of FeatureApi we will pass NavigationApi as component dependency. It is still possible to get from it required FeatureStarter, which is actually a proxy.

Note: You can’t replace FeatureApi with NavigationApi if the feature component requires something, except FeatureStarter (e.g. interactor or repository)

NavigationApi should be bound to AppComponentDependencyHolder. NavigationApi exists as long as Application.

AppComponent bind in Application

Let’s create the navigation graphs App and Detail.

In the App graph, the ListComponent and DetailComponent are only initialized after navigating to their screens. In the Detail graph, the initialization of the PdfComponent will happen after navigating to the Pdf screen, although the PdfFeatureStarter has already been added to the DetailDependencies, but as mentioned above, in fact, it is just a proxy.

Let’s figure out how to bind a component to the screen lifecycle.

Goals:

  • The component must survive a configuration change
  • The component should be destroyed when the screen is destroyed
  • The component must not be destroyed while the screen is in the back stack

Let’s create a store that will exist as long as the Application exists. The key in this storage will be NavBackStackEntry.id and the value will be the LifecycleEventObserver implementation containing BaseApi. By monitoring the Lifecycle.Event.ON_DESTROY message from the NavBackStackEntry, we will remove the BaseApi attached to the screen from the storage.

However, Lifecycle.Event.ON_DESTROY can fire on a config change, using NavBackStackEntry there is no way to tell if it’s a screen navigation or a config change. So let’s use Application.ActivityLifecycleCallback. When onActivityPreDestroyed is called, if a configuration change occurs - we’ll block the possibility of deleting BaseApi from the storage, and when onActivityDestroyed is called - we’ll unblock it.

Register ActivityLifecycleCallbacks in application

Let’s place the implementation of this class in the injector_compose module. Let’s create a component that returns an InjectorComposeApi and bind it to the AppComponentDependencyHolder.

In the UI feature, we will add a dependency on BinderApiToEntryLifecycle and oblige FeatureApi to implement the BinderBaseApiToLifecycle interface, which will allow us to bind FeatureApi when opening our screen. Hide the binding logic in the Composable function BindApiToEntryLifecycle

It turns out the following logic. We create a navigation graph, at each point of the graph there is a NavBackStackEntry, we pass it to the FeatureStarter. Next, the wrapper function above the screen is called. In this function, using BindApiToEntryLifecycle, we’ll bind FeatureApi and NavBackStackEntry.id, and when NavBackStackEntry is destroyed, we’ll unbind FeatureApi.

The previous article showed how to inject a SavedStateHandle containing screen arguments into a ViewModel using Dagger2. The created Inject function is the point of injection of all the necessary dependencies for the ViewModel and can also be used in the approach described here.

Thanks for reading.

All code from article could be reached out here

Umbrella IT is a globally oriented company that has been providing web and mobile development and IT consulting services since 2009.
- 350+ tech experts;
- 350+ successful projects (Hamleys, Kardex, Variety, Rolling Stone, 9GAG).
- TOP-100 best outsourcing company according to IAOP Global Outsourcing.

--

--