Dagger 2 and Jetpack Compose Integration

Alexey Glukharev
ProAndroidDev
Published in
3 min readMay 11, 2021

--

Highlighting the advantages of DI is not the purpose of this article, but almost all of the project needs it. In the official documentation, it’s easy to learn how to use Hilt with Jetpack Compose; however, in the real world, most of us have been using Dagger 2 for dependency injections.

Let’s start with how Hilt works under the hood with Jetpack Compose navigation. Thereafter, we will focus on the Dagger 2 solution. Before we begin, we must first define navigation.

Compose Navigation

Let’s define the navigation in Activity.

Add string constants for the navigation (i.e., for the advanced way you can store properties there).

In our Activity, we need to use NavController.
This class keeps the state of composables and tracks their back stack.
We must create the instance of this class in the composable hierarchy.

Next, we need NavHost. Using DSL inside NavHost, we define what our app will navigate to and what composables we should use for each screen.

Suppose that for each screen, we need a ViewModel. If our ViewModel has an empty constructor with no dependencies, it’s easy to use.

Again, in the real world, this is a pretty rare case. So, suppose that our ViewModel has some dependencies (e.g., Repository). Here, we are starting to use DI (as far as this article is concerned).

Hilt

Using Hilt, we can inject our ViewModel in quite a straightforward manner.

Now, let’s see what is the inside the hiltNavGraphViewModel() extension.

  1. Hilt uses LocalViewModelStoreOwner to identify the ViewModel owner. It might be Activity, Fragment, or in our case, NavBackStackEntry (provided by composable()). So, our ViewModel will work in the scope of NavBackStackEntry and close accordingly.
  2. The new ViewModel instance will be created in Hilt’s ViewModel Factory provided by generated ViewModelComponent.

Dagger 2

We now understand how it works with Hilt. So, let’s apply similar logic to Dagger 2. I would also like to mention that Dagger 2 offers a great advantage in that we can use various custom components for each composable screen.

Component, Model, and Scope
Here is all the same as usual.

The first important step is to create an extension similar to hiltNavGraphViewModel(). However, to make things clearer, let’s return lambda instead of complicated custom Hilt’s ViewModel Factory. Additionally, let’s also agree to name it daggerViewModel(). It’s just for naming, there is nothing related to Dagger inside.

What can we see in this extension? First, we simply create a new ViewModel instance using a custom factory.

Using this extension, we will create a Dagger Component (if needed, according to the ViewModel owner) and provide a ViewModel instance.

It would be easier to understand this code and parts where we create DI components if we suppose that `composable(){}` block is something similar from a scope perspective as Activity or as Fragment. So, we have two options for how (and where) we create a DI Component.

  1. Create Dagger Component inside the composable(){} block; this solution gives us a possibility using the same instance of this Component a few times in this block, providing multiple objects.
  2. Create Dagger Component inside the daggerViewModel extension’s lambda. This way may be useful if we need to get only one object from the DI with resolved dependencies. In our case, we need just only ViewModel in here. It means that DaggerScreen2Component will be created only if the current owner (NavBackStackEntry) must create a new instance of ViewModel.

Here is a repository with the full code of this example, which you can compile and run:

--

--