Making a Best Practice App #4 — Dagger 2

Patryk Poborca
10 min readAug 27, 2015

--

Edit* Video relating to this article

So this article is going to be about understanding dependency injection, testing and the various sorts of architectures available to you. For some of you, this may be the first time you hear about these topics, for others you know the indecisiveness that comes with trying to pick which DI library to go with and what design pattern to pair it with.

Let’s start with the topic most of you came here for first, Dagger 2. Dagger 2 is a dependency injection library (DI) which was created by Google, based upon Dagger 1 which was created by Square. Dagger 2 is slightly more performant according to some blog posts but it’s negligible. The biggest reasons to advocate Dagger 2 are:

  1. Dagger 1 issues with Proguard
  2. Dagger 1 generates unreadable code making debugging a pain
  3. Dagger 2 has better scoping to my understanding

So let’s get started and talk about what DI is, and how to use Dagger 2. To first understand DI, you must understand the principle of inversion of control. In essence it’s the idea that your class should not create new instances of classes that it depends on. This does not means you can’t instantiate Strings, but rather more complex classes.

As you can see above, that by having myClass rely on MyDependency we have created the beginnings of a Directed Acyclic Graph (DAG)

MyClass now depends on MyDependency

We have started the path to a potentially large and unforgiving dependency graph. Say we have dozens of classes which have a variety of dependencies, what happens when one of these has its implementation changed? A concrete example being the example below for a slightly larger dependency.

Bad Implementation

But why is the above a bad implementation? This is because the moment the implementation details of MyClass or MyDependency change we will have errors with how we’re instantiating MyClass. Now this isn’t an issue in a super small scope, but say we have several class which depend on MyClass. That means that the moment implementation changes, we will have to refactor code in N different places.

Good Implementation

Alright, that’s great, we basically did the same thing as we did with myClass, now what? Well ideally it would be nice if there was a factory object which could provide our dependencies for us..

Well that’s great, but I just wrote a large amount of boiler plate considering the simplicity of the classes which will only grow more complex as we get more and more dependencies! Just thinking about manually managing this in a growing project scares me!

To clarify, we would no longer have to depend on things being provided at the constructor level, we could rely on our factory…

Hello Dependency Injection

This is where dependency injection comes in, instead of having to write this factory method, and then access factory provider methods to grab instances of objects I depend on, I can simply annotate injectable fields and Dagger 2 will provide me an instance of this object.

And just like that, our dependency injectors knows to have its Factory provide instances of those objects. Dagger will examine these fields/methods/constructors and then instantiate/call them based on whether it has the ability to “provide” their parameters. This is where you come in.

In order to get Dagger 2 running in our project we need to do two generalized steps:

  1. Create a DAG
  2. Annotate injectable methods

You just learned how to annotate methods, so it just comes down to making a DAG. Dag as linked above is just a representation for dependencies, so that this way we happen upon an injectable field, we know how to provide anything our annotated classes need, no matter how deep. Dagger 2 uses Components and Modules to illustrate a DAG. I’m going to try to help you visualize what their purpose is, hopefully my paint illustrations help

Modules:

Modules are the providers of the factory that I listed above, modules are logical/arbitrary groupings of provider methods which Dagger will utilize in order to provide instances/parameters to injectable fields/constructor/methods. In fact a module has a annotation for its methods called @Provides this is what you will use in order to annotate your provider methods to help identify them for Dagger 2. The only parameter the @Module annotation takes is the includes list, which is a list of modules that our annotated module extends its grouping to.

tl;dr Modules are grouped nodes of DAG where the nodes are the @Provides methods.

Implementation

As you can see it’s the class MyModule which for our case is the grouping of nodes with the word “My” in their name. As you can see for the purpose of this demonstration this module is very arbitrary. So now we have the nodes of our DAG.

Components:

Components are also nodes, but their dependencies define the arrows of the DAG. Despite being nodes, they represent a FILTERED version of the module. So if the grouping of nodes is the module, the component is the gate/road which permits providers to be used outside of their module and components which list them as their module. The implementation of a component is an interface which has methods which will be implemented by a generated class in Dagger called DaggerMyComponent. Any interface method will expose its return parameter’s provider. Additionally, any interface method with a parameter will be essentially an injection method, a method to which you pass an object with injectable fields.

Let’s Elaborate

The above snippets are available in the demo package in my original MVPDagger2 demo. However we’re going to use my Clean Architecture repository for the remaining examples of this article, the next article of this series will go over the architecture/testing aspect of this repository. [I highly recommend taking a peek at the Clean Architecture repo if you’re not familiar with the subject matter]

Okay, so you somewhat understand the concept of Components, Modules, and DAG’s. But how does this help you, how can you utilize it? We’re going to dissect Tweeter API and its related components. Let’s set the stage. If we were writing a Tweeter API, what would it need? Well we need some sort of API to hit endpoints, that’s where OKHttp comes in. So let’s create a mock OKHttp class.

Next, how are we going to deal with getting this data, parsing it and returning it to our calling Activity/Fragment/View? Well that’s where our Retrofit class comes in.

Finally we can create a simple Tweeter API, right? Well, what about caching fetched tweets so they can be pulled up without hitting the the endpoint. That’s where we have our LocalDataCache.

And there we go! We have created all the classes that our Tweeter API will rely on, so let’s create our Tweeter API, all of the classes it depends on are completely finished.

So right now our mock classes simulate getting an arbitrary response from server, doing work on it via retrofit, and then formatting it via our Tweeter api. Or if we want, we can grab recently fetched tweets from our LocalDataCache (LDC).

Right now the application component just exposes the module’s Application provider, meaning that any dependency on this component or injectable object will have access to it.

So where do we start implementing Dagger? Well, since our LDC (LocalDataCache) requires a context so let’s create an AppModule & AppComponent to expose Context.

Module:

Component:

With our Application being available to be used by any dagger components which depend on ApplicationComponent we can proceed to create two new modules.

LocalModule:

NetworkModule:

Notice how providesRetrofit and providesDataCache both have parameters? Dagger’s annotation processor will figure out if it can provide those arguments. Due to Application being provided from ApplicationModule and OKHttp coming from the same module we can can create LocalDataCache and Retrofit. If Dagger finds out it can’t provide an object as a parameter you will get an error during compilation.

So, now if you wanted to have these completely decoupled, you could create two different Components NetworkComponent and an LocalComponent to expose their respective provided objects. However, for the purpose of this app, I want any component which needs access to the OkHTTP, Retrofit, and LocalDataCache objects to only depend on ONE component.

BaseComponent:

As you can see we have this component lists two modules, and lists that it depends on BaseComponent, so it will expose nodes from two different groupings (Modules). This is how our DAG looks like now.

The application component exposes the Application, thus when the BaseComponent DEPENDS on it, it shall receive an instance of the Application.

So with our modules providing Context, Local Data Cache, OKHTTP, Retrofit we have successfully exposed every Tweeter API dependency. If you remember its constructor takes a Retrofit and LocalDataCache instance. Retrofit takes OKHTTP, and LDC takes Context. All of these are exposed in our DAG thus allowing us to annotate Tweeter API’s constructor with

@Inject

Our BaseComponent will have a class generated which implements it called DaggerBaseComponent.

public final class DaggerBaseComponent implements BaseComponent

This class will have a builder style implementation, which we will have to provide Components it depends on, along with any linked module which has a non-default constructor. Since BaseComponent’s “Scope” (More on that later in the article) is meant for the entirety of the project, we create it inside the Application’s onCreate.

Something important to note, even though the BaseComponent lists both Network and Local Modules, we do not have to set them in the builder, this is because they have default constructors.

I save the generated components as static variables which are exposed via getter’s, this is because you do not want to rebuild the components thus recreating their provided objects. We only do this because the BaseComponent provides objects which should persist the lifetime of the application. Because Dagger is aware of Modules’ default constructors which have 0 parameters I have the choice not to provide a module as can be seen in the code below.

So, we have instantiated all of our “factories” which provide us instances of each exposed class. However, we’re missing a component which can Inject our objects into a class that needs them. Case in point, the only way for us to get a Retrofit right now is by doing this in the app.

CleanArchitectureApplication.getBaseComponent()
.getRetrofit();

However this sort of defeats the purpose of simply relying on our DI framework on injecting anything we need into our class. So in other words any @Inject annotations in a class will simply not be injected. This is because we need to create a method which takes an object with @Inject non-private annotated fields.

ActivityInjectorComponent:

Thanks to do this, we can now build a ActivityInjectorComponent to inject our MainActivity, so we could do this now:

If you’re interested as to how Dagger injects the tweeterApi variable, it’s pretty simple. By not making the variable private, generated code has the ability to simple set the field variables, which is why we pass the instance of MainActivity to the inject method.

Done?

Well, for the purposes of our demonstration we have a finished TweeterAPI injector. As you can see, Dagger 2 simplifies the whole factory provider pattern. So in our current implementation, each time we attempt to inject our object we will simply ask the provider for another instance.

This is where Scopes come in. There’s one that’s provided with Dagger 2 by default. It’s called @Singleton. However it’s a bit deceptively named, it implies that other scopes are not Singletons when that’s not the case! Scopes are user-managed, and you can easily create new Scopes.

The naming scheme is arbitrary as its lifetime is determined by my implementation. All I have to do is annotate my provider methods:

And a interface-level annotation for their components

If you dig into the generated files, you can see that in fact that a Scoped provider provides a “singleton”

UnScoped:

We grab a String each time from the provider, so unless the provider stores the instance itself it will return a new object each time

Scoped:

For scoped, we grab the Object instance and then check if it’s uninitialized, if it is uninitialized it will factory.get, otherwise we’ll return the initialized instance.

I could have used @MyRandomScope

Scopes are simply a way to logically group your component/modules together in terms of how long they should stay in existence before you re-instantiate them. There’s really only one rule to Scopes, each component-module relation ship can hold one scope, so if the component is scoped @MyRandomScope then the module provider methods need to annotated as well, and they cannot be @AnotherScope. Additionally, if you have a scoped component/module, any dependent components NEED to be scoped, and they cannot have the same scope.

What if I have multiple objects of the same type in my DAG?

This is where @Named comes in. You simply annotate your provider method, and now it has its own “type” for the purposes of the DAG. Example listed below,

In the above example, even though we have MyModule with to provider methods returning the same type MyClass we utilize the Named annotation to distinguish the two.

Finally there are a few other tools you can use such as

@Inject Provider<MyClass> myClass;
@Inject Lazy<MyClass> myClass2;

Where a Provider will instantiate a new myClass from the factory each time you do myClass.get() will instantiate a new version of the MyClass. Meanwhile the Lazy will delay instantiate until the first myClass2.get() this means it’s a lazy loader. That said I have yet to utilize either of these.

Continued Reading

If you are interested in how Dagger 2 can be used in conjunction with Testing, please check out Part 2

Thank you for reading my series, I hope it’s informative and helpful. If you have any questions or comments (ahem.. even hate mail) feel free to tweet me also I’d appreciate if you follow me too. Or simply make a comment here!

Looking for an Android Contractor? Look no further, contact me at: info@koziodigital.com

--

--