Dagger for Android: A Beginner’s Guide — Part 1
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
- 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.
- 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.
- 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.
- 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!