Dagger for Android: A Beginner’s Guide — Part 1

Avishek Khan
Monstar Lab Bangladesh Engineering
11 min readAug 31, 2019

Dagger has been around for a long time as the most popular dependency injection framework for Android. It’s been constantly evolving and adapting to developers’ needs ever since its birth leading to different ways of implementation varying in degrees of complexity and requiring a steep learning curve. But with the introduction of dagger-android, configuring Dagger within an Android project in 2019 is easier than ever.

Dagger was designed to be a cross-platform dependency injector for any Java-based project. It analyzes the dependencies, verifies them at compile-time and generates all the boilerplate code with a complete dependency graph under the hood — all at a cost of just a few steps to set up Dagger in the project. When used in an Android project, the process becomes a bit cumbersome at times. This has been made streamlined in the last few versions of dagger-android. With the removal of some interfaces and coalescing them into one, it has been made even simpler in the last update (2.24) which was released just last month (July ‘19).

This is intended to be a guide for beginners. In my opinion, the best way to learn Dagger is by working on a project and learn one thing at a time along the way. So I decided to write this three-part series based on the latest version of Dagger, not just for the absolute newbies, it’ll also work as a guide for my future self. I created this demo project for you (and me, of course) so that you can import it to Android Studio and set up Dagger all by yourself and learn interactively as we make progress through this article.

Special thanks to Atiqur Rahman for helping me out on the project.

Prerequisites

  1. The project has been written in Kotlin. If you’re not comfortable with it, you can still go through it and implement everything in Java on your own project.
  2. This project made use of ViewModels as part of the Android architecture components. If you haven’t played with them yet, I would highly recommend starting it right now.
  3. Network calls are performed using Retrofit. If you haven’t worked with Retrofit before, please take a look here to get an idea about what it does. That being said, knowing Retrofit is not crucial for this post to understand Dagger. All you need to know is that Retrofit uses an interface (that we define ourselves) in order to generate the implementation needed for network calls.
  4. While RxJava is not necessarily required to understand Dagger, it should be noted that RxJava call adapter factory is used with Retrofit in our project. This is not a prerequisite for this post. But if you haven’t used RxJava in your projects yet, I would highly recommend doing so.

What the app does

By the time we finish up everything, the app, when launched, will fetch 30 random images of cats through a network call and display them on a RecyclerView in MainActivity. If we click an item, a CatFragment will be loaded and a network call will be performed to fetch the URL of the selected cat image and load it in an ImageView.

The activity, fragment, recycler view, and their corresponding layouts, as well as the UI logic, ViewModels, repositories, and service, are already there except for the fact that it won’t work until we finish configuring Dagger. Some of the Dagger-specific classes have already been created in the di package. They are empty inside and we will fill them up step by step. By the time we’re done, I hope you’ll learn how to implement it in your own project. Without further ado, let’s dive right in.

Project overview

After cloning the project and importing in Android Studio, it would be better if you gloss over the packages in our project. The ui package contains the activity, fragment, view model, adapter and a mysterious ViewModelFactory class which we will come to later. The service, repository, model, and Dagger classes are placed in their corresponding packages. For simplicity, I didn’t create any more package except util that you can completely forget for now and base that contains just a BaseViewModel class which is not important for the purpose of our understanding.

Here’s the most important part — the direction of dependency. The dependency flow of our app is as follows:

Activity/Fragment > ViewModel > Repository > Service

You may also want to consider taking a look inside MainActivity.kt, it has a member dependency which is an instance of MainViewModel. Here’s how it looks:

The same goes for CatFragment :

Note: using ViewModel property in the activity or fragment like this is an absolute no-no. We’re doing this for the sake of understanding Dagger and by the end of part 3, you’ll come to know why it’s not recommended and how to do it in a better way.

MainViewModel and CatDataRepository have constructor parameters as their dependencies:

As you can see, MainActivity and CatFragment have a MainViewModel dependency each, which in turn has an abstract dependency on CatRepository. CatRepository is an interface and is implemented by CatDataRepository. CatDataRepository has an abstract dependency on CatService which is our Retrofit interface and is internally implemented by Retrofits auto-generated class.

That’s all you need to know for now and don’t care about the implementation details of any class. We just want mainViewModel property in our MainActivity to be set and we want Dagger to satisfy all the dependencies that come with it. First things first, let’s set up Dagger in our project.

Adding Gradle dependencies

If you take a look at our app/build.gradle file, you can see the following dependencies:

final dagger_version = '2.24'
implementation "com.google.dagger:dagger:$dagger_version"
kapt "com.google.dagger:dagger-compiler:$dagger_version"
implementation "com.google.dagger:dagger-android:$dagger_version"
implementation "com.google.dagger:dagger-android-support:$dagger_version"
kapt "com.google.dagger:dagger-android-processor:$dagger_version"

Here I used version 2.24 which is the latest at the time of writing this article. You can find the latest version of Dagger here. All the other dependencies included in the file have little to do with understanding Dagger. So, let’s move on.

Creating a custom Application class

First thing you need to do when setting up Dagger in your project is create a custom Application class, if not there’s one already, and make sure it’s registered in AndroidManifest.xml. In our project, there’s already an Application class, PurrfectApp, that I’ve written for you. Just add this field inside of it:

@Injectlateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any>

The @Inject annotation used here (and this is true for any other field annotated with it) is the way you tell Dagger to instantiate an object of its type and set the field to that object. This is called field injection. The DispatchingAndroidInjector class will be instantiated and the field will be set by Dagger itself. This class takes care of injecting objects into Android classes such as activities, fragments etc. and is used internally by Dagger.

Now implement HasAndroidInjector and return dispatchingAndroidInjector from the overridden androidInjector() function. Once you do it, PurrfectApp should look like this:

Creating the app component

Time to create a Component. It’s an interface extending AndroidInjector that Dagger implements to generate a class that contains all the stuff needed for dependency injection from the modules (more on this later) that we install into it. You can name it anything you want (but do make it meaningful) as long as you annotate it with @Component. Also, install AndroidSupportInjectionModule in it. Look at the code snippet from our AppComponent.kt below to see how I’ve already done all this for you:

This component is our key to injecting objects into our Android components (i.e.- activities, fragments etc.). It has a reference to our Application class, hence references to the activities, fragments etc. as well, and is created along with a complete object graph when PurrfectApp is instantiated by the system when we launch the app.

Now build the project and let Dagger generate the implementation of AppComponent. The name of the generated class will be the interface name prefixed with Dagger —DaggerAppComponent in our case.

Now when the build is finished, after the super.onCreate() line in onCreate() of PurrfectApp, call DaggerAppComponent.create().inject(this).

Now run the app and let it go into a crash to see the logcat message:

kotlin.UninitializedPropertyAccessException: lateinit property mainViewModel has not been initialized

This happened because we haven’t told Dagger that our MainActivity is injectable like PurrfectApp class. But instead of creating another component, we have to create a subcomponent for MainActivity.

Creating subcomponents

Create an abstract class annotated with @Module and name it anything you want to create a Dagger module. Inside the class, declare an abstract function annotated with @ContributesAndroidInjector that has a return type of the activity you’re binding to the subcomponent. In our project, we named the module ActivityBindingModule. Here’s how it looks:

Note that the name of the function is arbitrary and isn’t called at runtime. It’s the return type that matters which is the activity you’re making the subcomponent for.

Now install this module in the application component:

In the onCreate() function of your MainActivity class, call AndroidInjection.inject(this) before the super.onCreate() call.

Now you can inject any field you want in our MainActivity using the @Inject annotation:

But if you build the project now, you will get a compilation error that says:

error: [Dagger/MissingBinding] com.avi5hek.purrfectapp.ui.MainViewModel cannot be provided without an @Inject constructor or an @Provides-annotated method.

This is Dagger’s way of telling you that it doesn’t know how to instantiate our MainViewModel class. We need to provide the dependency so that Dagger knows where to find it.

Providing dependencies

One way we can accomplish this is called constructor injection. Go to MainViewModel class and insert @Inject between the class name and its constructor, like this:

This way Dagger knows where to get the instance of the requested type (i.e.- MainViewModel). But since MainViewModel has its own dependency on CatRepository, we need to provide it too. CatRepository is implemented by CatDataRepository, let’s do the same for this class as well.

Now Dagger knows how to instantiate CatDataRepository, but MainViewModel is asking for CatRepository and Dagger isn’t so sure about which of its instances should be used to resolve the dependency. Let’s tell Dagger that it should use CatDataRepository when we need a CatRepository instance. To do that, create a new abstract module class just like before and put inside an abstract function that takes CatDataRepository as a parameter and returns CatRepository. Annotate it with @Binds so that Dagger knows that it should bind CatRepository to CatDataRepository. Go to our CatModule.kt file and add this snippet:

Just like before, the name of the function doesn’t matter. But name it meaningfully for better readability. Don’t forget to install the newly created module in the application component:

Now, if you build the project, you will get a different compilation error. This time CatService cannot be provided without an @Provides-annotated method. But unlike CatRepository, this interface is supposed to be implemented by Retrofit’s generated class behind the scene. Since we cannot inject its constructor as we did before, we need to instantiate the class ourself and provide it to Dagger. We can get the object of that class by calling create() on the Retrofit instance and bind our Service interface to that. We can do it by using a static @Provides function in our CatModule class:

This way, when Dagger needs a CatService instance, it will get it from the return statement of this function. But as you can see, this function has its own dependency on Retrofit. You can provide it exactly the way we did for the CatService instance — declare a static function with @Provides annotation right below bindCatRepository(), build Retrofit instance and return it. But I would recommend creating a different module for network-related dependencies as we did in our project with the NetworkModule. I’ve already written the code in this module for you. You don’t need to understand everything written there, just take a quick look, you can see some other examples of@Provides functions. Don’t forget to install this module in the AppComponent.

Pro tip: keeping different types of dependencies in separate modules is a good practice in terms of separation of concern.

Note that, the order of the provider functions is totally arbitrary — you don’t need to keep a strict order of function declarations for the dependencies since Dagger will create the graph in the correct order no matter how you organize the @Provides or @Binds functions.

If you build the project, the compilation will fail again. Because Retrofit depends on SchedulerProvider which has a binder function declared in AppModule for the sake of separation of concern. Add @Inject to the AndroidSchedulerProvider constructor and install the module into AppComponent.

Now if you build and run the app, it will show you a list of cute cat images which means our dependencies have been properly resolved except for the fact that when you click an image from the list, it will cause a crash. In the logcat you’ll see something like this:

kotlin.UninitializedPropertyAccessException: lateinit property mainViewModel has not been initializedat com.avi5hek.purrfectapp.ui.CatFragment.onViewCreated(CatFragment.kt:36)

We need to inject MainViewModel into CatFragment as well. For that, we have to make CatFragment injectable by creating a subcomponent for it, just like we did for the activity before. To spare you the trouble of repeating the subcomponent creation procedure, I’ve already written the code in MainFragmentBindingModule. You just need to install it into AppComponent so that it looks like this:

Unlike activities, where we call AndroidInjection.inject(this) in onCreate(), we call AndroidSupportInjection.inject(this) from the onAttach() function of the fragment that we want to inject before the super.onAttach() call. Also, annotate mainViewModel property with @Inject.

Note: call AndroidInjection.inject(this) before super.onAttach() if you’re not using Fragment from the support library in your own project.

Now run the app and see the magic. Everything works fine, right? Not so fast, chief! Every time mainViewModel field is set in our activity or fragment, Dagger instantiates a new object of it. Not just the MainViewModel, this is true for the whole dependency chain — each and every object in the chain is instantiated over and over again! This is bad because the cost of object instantiation is high and if the project gets bigger, which every project eventually does, the runtime overhead of this implementation will become huge.

This can be fixed by using scopes. For this part of the guide, we haven’t used anything from the scope package. This has been discussed in part 2. There’s also ViewModelKey and ViewModelFactory files that we haven’t used yet. Let’s save them for the third part of the series.

In your projects, you can bind/provide any number of dependencies in as many modules you like, just make sure the modules are installed in the appropriate component/subcomponent such that no higher-level instance depends on a lower level one. For instance, objects provided in the module installed in a subcomponent can depend on parent component modules, but not the other way around. Check out part 2 where we discussed this at great length:

Please feel free to share your thoughts. You can reach out to me on Twitter: @Avishek_Khan. You might also want to check out some tech posts on our blog. Until next time, happy injecting!

--

--

Avishek Khan
Monstar Lab Bangladesh Engineering

Software Engineer. I love Java as a coffee drink and Kotlin as a programming language. Passionate about science, technology, fitness, traveling, and tequila.