Note: This blog post will be more geared towards Dagger within the context of Android development. I think the terms that software developers use to describe certain concepts makes simple things seem way more difficult than it is.
Dependency injection is one of them. When I first heard of the term a few years ago, my brain went, “Hmm, that sounds like a concept that will take me years to fully understand”. But nope, all it really means is you pass in instances of other objects/classes in the initialization block of a class.
In Java land, it might look something like this.
|
public class Kitchen { private Knife knife; public Kitchen(Knife knife) { this.knife = knife; } } |
In Ruby land, probably something like this
|
class Kitchen attr_reader :knife def initialize(knife) this.knife = knife end end |
Yes, I realize these are probably not the most extensive examples, but I hope that the readers will get the point. All dependency injection really is involves passing in instances of other objects into the target class that the target class may need to do stuff.
Enter Dagger
If you do Android development at all, you’ve probably heard of a library called Dagger. Dagger is a dependency injection framework that helps you manage dependencies in your Java applications. At the time of this writing, there are two versions of Dagger: Dagger 1 and Dagger 2. Dagger 1 was written by the fine folks are Square and Dagger 2 was written in collaboration between Square and Google, and is currently maintained by Google.
Dagger 1 vs Dagger 2 are kind of like Angular 1 vs Angular 2 (in a way…). Both Dagger 1 and Dagger 2 are dependency injection frameworks that aim to solve the problem of managing dependencies throughout your codebase. Both are independent of the Android framework and can be used in any Java application.
Dependency injection looks relatively simple, in the above code sample with the Knife
class being injected into the Kitchen
class. So if it looks simple, why do we need a dependency injection framework?
In real life application codebases, specifically in Android codebases in my case (since my experience working with Java is limited to Android), I found myself passing injecting dependencies in almost all of my utility classes that I’ve written to help encapsulate certain functionality. For example, one thing that I generally do in my Android applications that’s backed by an API is store user tokens in SharedPreferences
. I generally end up writing a class called TokenUtil that takes in a Context as a dependency since you need a Context in order to be able to utilize SharedPreferences. So something like this.
|
public class TokenUtil() { private Context context; public TokenUtil(Context context) { this.context = context; } } |
And in my Activity classes, I always end up having to initialize my TokenUtil with the Context. For example:
|
public class MainActivity extends AppCompatActivity { private TokenUtil mTokenUtil; @Override protected void onCreate(Bundle savedInstanceState) { mTokenUtil = new TokenUtil(this); } } |
This seems relatively innocent and simple, but then again, this is a simple example. I’ve seen classes that take a large amount of dependencies in order to function. And in large codebases, it becomes complicated to manage these dependencies just to utilize different classes throughout the codebase. Dagger helps to manage this chaos.
So why write a post about Dagger 1 instead of Dagger 2?
To be honest, I don’t know. I was chatting with PluralSight about authoring a course on Dagger, and they suggested that we start with Dagger 1. The reasoning was that there are still probably large amounts of codebases on Dagger 1 that have not upgraded to Dagger 2, so that there are probably many professional Android developers who still have to work with the older version of Dagger.
With that said however, if you’re starting a greenfield project, it makes no sense to go with Dagger 1 as it is no longer maintained. Just go with Dagger 2. If you’re in a situation where you still have to work with Dagger 1 however, read on.
How to use Dagger 1 within Android
As mentioned above at the beginning, this blog post will be more geared towards Dagger 1 within the context of Android development. Dagger is said to have a high learning curve. I found this to be…. not really the case. Yes, if you want to learn every little parts of Dagger, perhaps. But to start using it to reap the benefits of using a dependency injection framework, you can get started within about an hour or so. I’ll go over how to set Dagger 1 within the context of an Android application.
1. Add Dagger to your app’s build.gradle file
|
dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:26.+' compile 'com.android.support.constraint:constraint-layout:1.0.2' compile 'com.android.support:design:26.+' compile 'com.android.support:support-v4:26.+' // Dagger compile 'com.squareup.dagger:dagger:1.2.5' compile 'com.squareup.dagger:dagger-compiler:1.2.5' } |
At the time of this writing, I believe that version 1.2.5 is the latest for Dagger 1. And since Dagger 1 is no longer maintained, it will likely be the last version released for Dagger 1.
2. Create an Application class that extends from Application
Create an Application class that extends from Application class, and set it as the app’s Application in AndroidManifest. So if your app’s name is PocketLifts (name of a side project I’m working on), you might name the class like this.
|
public class PocketLiftsApplication extends Application { } |
And in your AndroidManifest, set the PocketLiftsApplication as the app’s main Application class.
|
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.pocketlifts.pocketlifts"> <application android:allowBackup="true" android:name=".PocketLiftsApplication" .....> |
3. Create your Dagger modules to define your dependencies
This is where I’ll have to pause a bit and explain something called the Object Graph. Remember how the TokenUtil class required a Context class? In this case, TokenUtil has a dependency on the Context class. What if you have an object that has multiple dependencies? And what if some of those dependencies have other dependencies? You can imagine that you could draw a graph of some sort with different dependencies linking to each other via a line. Dagger draws and manages this graph for you. But in order for Dagger to be able to draw and manage this graph, you need to tell Dagger about the dependencies you want Dagger to manage in your application. We define these dependencies in Dagger modules. Let’s first write a module that will define the godfather of all dependencies in Android development, the Context class. We’ll create a class called AndroidModule where we’ll define all classes/dependencies that comes from the Android SDK.
|
@Module(library = true) public class AndroidModule { private PocketLiftsApplication application; public AndroidModule(PocketLiftsApplication application) { this.application = application; } @Provides @Singleton @ForApplication Context provideApplicationContext() { return application; } } |
The @ForApplication
annotation is a custom annotation that ensures that the context provided by Dagger can be distinguished from the main application’s context. To define the @ForApplication
annotation, create an interface that looks like this.
|
@Qualifier @Retention(RUNTIME) public @interface ForApplication { } |
In the AndroidModule, the @Provides
annotation marks that the provideApplicationContext
method will make sure that Dagger will “provide” a Context
when needed and the @Singleton
annotation ensures that the Context
will be a singleton.
Next, let’s create a module called PocketLiftsModule
where we’ll define our custom dependencies specific to our application. In this PocketLiftsModule
, we’ll have it provide the TokenUtil
class mentioned above that takes in a Context
as a dependency.
|
@Module( complete = false, injects = { LoginActivity.class, MainActivity.class } ) public class PocketLiftsModule { @Provides TokenUtil provideTokenUtil(@ForApplication Context context) { return new TokenUtil(context); } } |
In here, the @Module
annotation takes in a few parameters. The complete = false
annotation signifies that the module is allowed to have missing dependencies. This complete variable is set to true as default, but here we’re setting it as false so that our code compiles. The more important parameter that we pass in is the injects parameter where we define the classes in the codebase where we’ll be injecting the dependencies that are defined in the module. In our case, I want to be using Dagger to inject and utilize TokenUtil
in the LoginActivity
class and the MainActivity
class. The provideTokenUtil
method takes in a Context as a parameter and it returns a new TokenUtil
that’s initialized with the context.
4. Inject your modules in the Object Graph in PocketLiftsApplication
With our Dagger modules defined, let’s go back to our PocketLiftsApplication to inject our modules into the object graph so that Dagger knows what dependencies we have in our application.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
public class PocketLiftsApplication extends Application { private ObjectGraph graph; @Override public void onCreate() { super.onCreate(); graph = ObjectGraph.create(getModules().toArray()); } protected List<Object> getModules() { return Arrays.asList( new AndroidModule(this), new PocketLiftsModule() ); } public void inject(Object object) { graph.inject(object); } } |
Here, we set the Dagger modules that we have just created and create the ObjectGraph by setting the modules as dependencies. Then we create a new method called inject
on our Application class that calls the inject
method on Dagger’s object graph.
5. Create a BaseActivity class that initializes Dagger
We’ll have to create a BaseActivity class that extends from AppCompatActivity, and then initialize Dagger by overriding the onCreate class. Then we’ll have MainActivity and LoginActivity extend themselves from BaseActivity so that Dagger can be used to inject dependencies in those two classes.
|
public abstract class BaseActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); ((PocketLiftsApplication) getApplication()).inject(this); } } |
|
public class MainActivity extends BaseActivity { // Typical Android Activity stuff here } |
|
public class LoginActivity extends BaseActivity { // Typical Android Activity stuff here } |
6. Inject the TokenUtil dependency in LoginActivity (or MainActivity)
Dagger is all set up and ready to be used. All you have to do to initialize TokenUtil using Dagger is define the instance of a class using the @Inject
annotation.
|
public class MainActivity extends BaseActivity { @Inject protected TokenUtil mTokenUtil; @Override protected void onCreate(Bundle savedInstance) { // mTokenUtil can now be used without passing in Context as a dependency String token = mTokenUtil.getToken(); } } |
And… that’s pretty much it for setting up and using Dagger as a dependency injection framework.
So what is this object graph thing?
Remember how I mentioned Dagger’s object graph a few times throughout this blog post and how we set it up in our PocketLiftsApplication class? The way Dagger manages dependencies is by building a “graph” of dependencies in the methods marked with the @Provides
annotation.
For example, our provideTokenUtil
method takes in a Context
as a dependency. Dagger sees that the TokenUtil
requires a Context
as a dependency, so it looks through the different modules that you set for the object graph to find a context. In our case, it’ll see that there are no Context
in the PocketLiftsModule
class, thus it’ll look in the AndroidModule
class to find the Context class. It’ll then take that Context to inject and initialize the TokenUtil
class and then provide a fully initialized TokenUtil
class where it’s initialized it via the @Inject
annotation.
Conclusion
This is just a quick overview of how to set up and use Dagger. There are definitely more configurations that you can make to use Dagger, but this guide should get you quickly set up and running.