Dagger 2 for Android Beginners — Advanced part I

Hari Vignesh Jayapalan
10 min readDec 23, 2017

--

This story is the sixth part of the series, Dagger 2 for Android Beginners. If you did not read the previous one, you can start from below.

Series Pitstops

Previously on Dagger 2..

We analyzed the anatomy of the generated class by the dagger and we spotted how dagger utilizes the builder pattern to deliver us the required dependencies.

We also played around and saw a basic example of using @module and @provides annotation.

Preface

This post might be little long. I usually keep my posts not extending 800 words — just to serve justice to my reader’s grasping capacity. I wanted to split into parts but the reason why this post is like a marathon is, if you have a long break in the middle, while solving hard dependency problem, there are some chances that you may get lost.

But on the safer side, I’ve included checkpoints in this post. Look for a 3 dotted line or the word (Checkpoint) in parentheses. That’s where you take a small break and allow your mind to process and absorb — this is for the beginners who are learning Dagger 2 and DI.

Why am I telling you all this? well, I’m a UX guy as well — I value empathy and user experience in everything :-P
Happy reading!

House Android

Image credits: http://androidcentral.com/

Till now, we were playing with normal Java project. I hope most of you would now have an idea about Dependency Injection and how Dagger 2 helps us in achieving it. Let’s now dive into a real-time Android scenario and try to build our project using Dagger 2.

In order to get everyone in the same page, like Google code labs, I’ve created a small kickstarter project. Our objective will be to eliminate hard dependencies in this project.

Kickstarter Project

Concept inspiration

This concept of explanation was inspired by one of the GDD talks of my good friend Chintan Soni. I would like to thank him for allowing me to use his concept and few of his works in this project.

Project Description

This is a very simple project — it fetches random users using Random Users API and displays it in a RecyclerView. I’ll not be spending much time on explaining the project — may be just an abstract. But please read the code for deeper understanding, so that our Dagger 2 integrations will become easier.

#Classes and Packages

  • MainActivity.java — Requests the API and displays the items in RecyclerView
  • Model Package — POJOs for the API response, created using JSON Schema to POJO
  • RandomUsersAdapter.java — Adapter for RecyclerView

Dependencies involved

In order to display random users in a RecyclerView via API call, following dependencies and libraries are involved.

  • Retrofit — for making API calls
  • GsonBuilder & Gson — for JSON parsing and manipulation
  • HttpLoggingInterceptor — for logging network operations
  • OkHttpClient — client for Retrofit
  • Picasso — image handling (in Adapter)

As we saw in our previous examples, in our MainActivity, we have the following dependencies. And each time MainActivity is called, these instances are created again and again.

(Checkpoint)

Problems to solve

If you look at our MainActivity, you may notice the following problems

#Clumsy initializations

When you look at our MainActivity’s onCreate method, you might find all initializations and it looks very clumsy. We can’t keep adding more initializations on the go. We need a proper way to do so.

#Testability

We also need to figure out a way to test our code. Even Picasso in our adapter breaks the rule of testability. Yes, it would be great to inject Picasso object via constructor would be good too.

Let’s make it more complicated

The dependencies that I mentioned above in our MainActivity was just to make you comfortable with the kickstarter project. If we go deep, there may be additional dependencies that may be present in our MainActivity — on a typical real-time project. So, let’s add additional dependencies to our MainActivity.

Refer the following branch for adding additional dependencies

Apart from the above dependencies, the additional ones are

  • File dependency — for maintaining the cache
  • Cache — for network cache
  • OkHttp3Downloader — a downloader that uses OkHttpClient to download images
  • Picasso — for handling network images

Now, the complete dependencies will look like this

Just showing only onCreate() in this gist

(Checkpoint)

The Secret

Many of us (including me) found DI and Dagger 2 little difficult because something was missing. A small piece to connect the nodes was missing. The missing link or the secret is nothing but the dependency graph

Dependency Graph

The Dependency graph is nothing but a diagram, explaining the dependencies of the classes via arrow marks or lines. Scribbling a D-graph for the projects will really make our implementation a lot easier (you will realize in the end). Here’s the DI graph for our project.

Dependency Graph

The green colour boxes signify that they are the top order in dependencies i.e. they are not wanted by anyone. They just need the below dependencies.

How to read/traverse the graph diagram? It’s like Picasso has 2 dependencies — OkHttp3Downloader and Context.

To get random users from API, you need Retrofit. That, in turn, requires 2 dependencies — GsonConvertFactory and OkHttpClient. They, in turn, need their own dependencies and so on.

Take your time, see the code in MainActivity and the diagram side by side for better understanding

(Checkpoint)

Handling Dependency Injection with Dagger 2

Please refer the following branch, you will find Dagger 2 implementation

Note :

  • RandomUsersAPI and RandomUsersApi are same. Just the typo error in creating the diagram.
  • RandomUsersComponent will be different when you compare the above branch and the gist that I use from here. I suggest you to take the following branch and walkthrough with the gists. Keep the above branch for reference.
  • Please don’t forget to star the project if it has really helped you in learning

Step 1: Setup Dagger

For setting up Dagger 2, please refer app’s build.gradle in the above branch or just add the following lines in your build.gradle file.

Remember to update the latest version as you proceed

Step 2: Creating Component

A component will act as a public interface for your entire dependency graph. The best practice of using a component is to expose only the top level dependency and keep other inner dependency under the hood.

That means, I’ve highlighted the classes in green colour in our dependency graph. They are the top level dependencies — RandomUsersAPI and Picasso. Let’s expose only them.

Create a component called RandomUserComponent and expose the following classes RandomUsersApi and Picasso.

Now, how will the component know where to get the dependencies RandomUsersApi and Picasso from? That’s where modules come in.

Step 3: Creating Modules

Modules will provide under the hood dependencies to the outermost dependencies — RandomUsersApi and Picasso.

We now need to move the code from MainActivity to different modules. By looking into the D-Graph and component, we can decide what modules we need.

First is, RandomUsersModule — which provides RandomUsersApi, GsonConverterFactory, Gson and Retrofit

Second, PicassoModule — which provides Picasso and OkHttp3Downloader.

For Retrofit in our RandomUsersModule, we need OkHttpClient. Which in turn needs few dependencies. So why not create a separate module for it?

Let’s create OkHttpClientModule — which provides OkHttpClient, Cache, HttpLoggingInterceptor and File

Our higher level modules are ready. But PicassoModule and OkHttpClientModule needs Context and we will also face other situations where we need Context. So why not a module for it?

Step 3: Connecting all Modules

Now, we have all modules and component in place — like the image shown below. But how do we pass the Context to other modules? we need to link the modules that are dependant with reach other.

Here’s where includes attribute comes to play. includes attribute include other module dependency involved in the current module.

what all modules need to be included?

  • RandomUsersModule needs OkHttpClientModule
  • OkHttpClientModule needs ContextModule
  • PicassoModule needs OkHttpClientModule and ContextModule. But since OkHttpClientModule is linked with ContextModule already, let’s include only OkHttpClientModule

By providing the above, we have linked all modules.

Linked all modules :-)

Step 4: Educating Component

Now, all our modules are connected and can communicate with each other. We now just need to tell or educate component on which modules that they need to depend on to provide the dependencies.

Like telling modules about their dependencies with includes attribute, we need to tell component about its dependencies with modules attribute.

Considering the needs of the components (methods — getRandomUserService() and getPicasso()), let’s include the module RandomUsersModule and PicassoModule in our component using modules attribute.

Component and modules are connected :-)

Step 5: Build it

It’s time to build the project. If you’ve done all the above steps correct, Dagger would have created us RandomUserComponent using builder pattern which now can provide dependencies are per our request.

Now, if we look at our MainActivity — we can easily acquire Picasso and RandomUsersApi with the help of RandomUserComponent.

Please refer the branch, there might be some modification to the adapter constructor to pass on the Picasso object.

Step 6: Congratulate yourself!

Yes! you did it. You used Dagger 2 in an Android application. Congratulate yourself and take a break (Checkpoint). Feel free to play around with the generated class as well.

But there’s a problem!

What? what problem?

Every time when you call <DaggerComponent>.build(), it creates new instances of all the objects or dependencies that you’ve instructed it to provide it for you. So, in that case why Dagger doesn’t know that I need only a single instance for Picasso? In other words, how do we tell Dagger to provide us singleton(single instance) dependency?

That’s where @Scope annotation comes in for our rescue.

@Scope Annotation

@Scope annotation tells dagger to create single instance, even if <DaggerComponent>.build() is called many times. It will make the dependency work as singleton.

We need to create a new interface to create our custom scope.

@Retention — is the policy of retention of the annotation. Here we have instructed to retain the annotation till class. Read more about retention here.

Custom Scope Usage

To use our custom scope, we need to start at the component level and then put on every method which we need as a singleton.

That’s how we create the single instance!

Now, one more problem!

Typically, for every app we’d be using two types of context. AppicationContext and Activity context. How do we provide it? for ApplicationContext, we can use our ContextModule to provide. So, let’s create another module called ActivtiyModule to provide Activity context.

But this doesn't solve our problem. As our Dagger will get confused on which context to use. As it has 2 modules each providing Context, it will be throwing an error.

How can we tell Dagger to use ApplicationContext for this dependency and Activity context for this dependency? @Named annotations will do that job for you.

@Named Annotations

This annotation helps us to differentiate the Context. Let’s see how to use them. We can differentiate the method context() in ActivityModule and ContextModule by adding the @Named annotation as below.

Then, we tell Dagger to use the respective Context, as below

Alternative to @Named annotation — @Qualifier

Alternative to @Named annotation is @Qualifier annotation. For creating ApplicationContext using @Qualifier, we need to create a separate @interface and use it where ever necessary. Let’s create an @interface ApplicationContext first.

Then we map this interface to the dependency provider method — context()

Then we tell all the parameters to use this context.

Refer the following commit for the @Qualifier annotation usage.

TL;DR

Till now, we took a sample kickstarter project and attempted to decouple and inject the dependencies using Dagger 2 APIs and annotations.

We also saw 3 new annotations. One is @Scope — which is used to get singleton dependencies. Next is @Named — which is used to differentiate dependency providers. Another one is @Qualifier annotation, an alternate to @Named annotation and we saw the implementation of both.

What’s Next?

Till now we saw only application level dependencies. Next we’ll see about activity level dependencies and also creating multiple components and interact with them as well.

Also, checkout my other stories.

Please don’t forget to hit star on the GitHub Project.

Thank you for using your precious time to reading this article. Liked it? Clap your 👏 to say “thanks!” and help others find this article.

--

--

Hari Vignesh Jayapalan

Android Dev at WeTransfer| ex Backbase | #1 Google AAD | Public Speaker | Blogger | Creative Coder & Logical Designer