Adventures in Dagger2 for Android

Adventures in Dagger2 for Android

Adventures in Dagger2 for Android

Introduction

I’ve spent the last eight or so months working as an Android application developer and in that time I’ve managed to pickup a few handy tricks, none quite so helpful as Dagger2. The purpose of this blog post is to document and share what I know about using the staple dependency injection library, and why it is useful. Before we get started, there are tons of great tutorials out there already and I will formally recommend That Missing Guide: How to use Dagger2 by Gabor Varadi and the official users guide maintained by Google.

I first heard about Dagger when I was starting at a new job and, as so often is the case, I inherited an existing code base. The first question I had when I learned about Dagger: why?

Why Inject when I can build

Lets consider a toy example to represent a party dip:

public void entryPoint() {
	ArrayList<String> ingredientList = new ArrayList<String>();
	ingredientList.add("Beans");
	ingredientList.add("Cheese");
	ingredientList.add("Sour Cream");
	ingredientList.add("Jalepenos");
	Ingredients myIngredients = new Igredients(ingredientList);
	Container dipBowl = new Container();
	Dip beanDip = new Dip(myIngredients);
	dipBowl.setDip(beanDip);
	startTheParty(dipBowl);
}

Class Definitions

// The juicy contents we care about at the end of the day
class Dip {
	Ingredients dipIngredients;
	public Dip(Ingredients ingredients) {
		dipIngredients = ingredients;
	}
}
// How we plan on getting it from point A to point B
class Container {
	Dip dip;
	public Container(){}
	public setDip(Dip newDip) {
		dip = newDip;
	}
}
// The inner workings of the deliciousness that we don't always need to care about
class Ingredients extends HashSet<String> {
	public Ingredients(List<String> ingredients){
		for(String ingredient : ingredients){
			add(ingredient);
		}
	}
}

There’s a fair amount of setup for our party. And this is just the setup for one kind of dip! It is not uncommon to swap implementations for a class. In the dip metaphor, we have different flavors of dip for different occasions.

Dagger can take our example entry point and transform it into something much more concise:

@Inject
Container dipBowl;

public void entryPoint() {
	startTheParty(dipBowl);
}

Our design won’t work quite yet though the@Inject annotation informs dagger that the class has a dependency, in this case on a Container. We can also use the @Inject annotation to inform dagger how it should build an instance of an object that is being injected. In the latter way, each argument to the constructor will be interpreted as a dependency.

class Container {
	Dip dip;
	@Inject
	public Container(){}
	public setDip(Dip newDip) {
		dip = newDip;
	}
}

In this case dagger will know to use the empty constructor when building a Container. This setup should compile but we’re lacking a critical ingredient. Dagger will happily build us a container but it has no idea about dip and as such, will give us a container without any dip.

Now there are multiple ways dagger can handle this of course. We could implement a new constructor, or change the existing one, to accept a dip argument. Then we’d continue by instructing dagger on how to construct a Dip instance.

But, lets say that we can’t modify Container. Then we could use a Module. They are a core feature of dagger that can be used to define the build and configuration for more complicated objects.

@Module
public class PartyModule {
	@Provides
	Container providesContainer(Dip dip){
		Container c = new Container();
		c.setDip(dip);
		return c;
	}
}

We can now add PartyModule to our dagger configuration (not yet discussed) and now dagger will know to give us a Container that has had its dip set.

To complete the example we need to annotate Dip so dagger knows how to build it:

class Dip {
	Ingredients dipIngredients;
	@Inject
	public Dip(Ingredients ingredients) {
		dipIngredients = ingredients;
	}
}

And specify how to build ingredients. Since we want specific ingredients we can use our module to define exactly what we want.

@Module
public class PartyModule {
	@Provides
	Container providesContainer(Dip dip){
		Container c = new Container();
		c.setDip(dip);
		return c;
	}

	@Provides
	Ingredients providesIngredients(){
		ArrayList<String> ingredientList = new ArrayList<String>();
		ingredientList.add("Beans");
		ingredientList.add("Cheese");
		ingredientList.add("Sour Cream");
		ingredientList.add("Jalepenos");
		return new Ingredients(ingredientList);
	}
}

With that setup complete, we can now use our trimmed down entry point!

So far we’ve seen how it can clean up our code, but some may still be asking why it’s useful, after all that we’ve only moved the same code across a few different files and achieve the same goal.

Here’s why: by setting up dependency injection, our entry point no longer needs to care about how to setup it’s dip bowl. It can get straight to doing what it is responsible for, starting the party. This also enforces the object oriented design principle of dependency inversion, which helps keep our code neat, tidy, and maintainable.

Applied Example

I think that’s enough with text based examples for now. Let’s take a look at how we could use this tool in Android. Refer to the docs to see how to configure the latest version of dagger 2 in your android project.

Note:

if you wish to use kotlin simply add: apply plugin: 'kotlin-kapt' after applying the kotlin-android plugin and replace annotationProcessor with kapt.

Lets take this as an example we wish to implement:

Class dependency graph

Train
Conductor
Engine
Rails
Water
Heat Source
Fuel

Here we have a Train that requires a Conductor, and Rails. Conductor requires Water to drink and stay hydrated during his shift, along with access to an abstract Engine they can drive. The Engine in turn depends on Water and an abstract HeatSource to generate steam. Finally the HeatSource depends on some type of fuel to expend and generate heat.

Implementation

Lets begin with the dependencies we will need:

implementation "com.google.dagger:dagger:$dagger_version"  
implementation "com.google.dagger:dagger-android:$dagger_version"  
implementation "com.google.dagger:dagger-android-support:$dagger_version" 
kapt "com.google.dagger:dagger-compiler:$dagger_version"  
kapt "com.google.dagger:dagger-android-processor:$dagger_version"

You can set dagger_version in the module level build.gradle file like ext.dagger_version = '2.22.1'

Also if you don’t plan on using kotlin replace kapt with annotationProcessor

Next, we will create an application class.

MainApplication.java

public class MainApplication extends DaggerApplication {
  @Inject  
  Train train;
  @Override  
  public void onCreate() { 
    super.onCreate();  
    train.run();
  }  
  @Override  
  protected AndroidInjector<? extends DaggerApplication> applicationInjector() {  
    return DaggerTrainApplicationComponent.create();  
  }  
}

By extending DaggerApplication we get injection automatically in super.onCreate(). We will still need to define our train. We will do that first and get to DaggerTrainAppliactionComponent later.

Train.kt

class Train @Inject constructor(  
  private val conductor: Conductor,  
  private val rails: Rails  
) {
 fun run() { 
   var remaining = rails.distance  
   while (remaining > 0) {  
     val step = conductor.drive()  
     if (step > 0)  
       remaining -= step  
     else {  
       Timber.i("ran out of fuel")  
       break  
     }  
   }  
   if (remaining < 1)  
     Timber.i("$conductor made it to ${rails.destination}")  
 }  
}

Notice how dependencies on Conductor and Rails are defined using parameters to the constructor. Because this constructor is @Inject annotated, dagger will get us those objects too when we ask it to build a Train.

Conductor uses an abstract, lets see what that looks like:

Conductor.kt

class Conductor @Inject constructor(water: Water, private val engine: Engine) {  
  init {  
    drinkWater(water)  
  }  
  
  @Throws(ExceptionInInitializerError::class)  
  private fun drinkWater(water: Water) {  
    if (!water.isEmpty()) {  
      water.consume()  
    } else {  
      throw ExceptionInInitializerError("Conductor needs water to complete his drive!")  
    }  
  }  
  
  fun drive(): Int {  
    engine.consumeFuel()  
    return engine.generatePower()  
  }  
}

So after drinking some water, our conductor can get down to business and drive our engine by calling some functions defined by the abstract base class. Now lets take a look at how we can associate a concrete implementation of Engine with the abstract Engine that is injected in this class.

To achieve this we use the dagger @Module and @Binds annotations. We annotate a class with @Module to let dagger know that it will define some useful builders or relationships it can use when attempting to build things we ask for. @Binds specifically annotates an abstract function that returns an abstract type and accepts a subclass or implementing type as an argument. With that lets see the sample:

BindingModule.java

@Module  
public abstract class BindingModule {
  @Binds  
  abstract Engine providesEngine(SteamEngine e);
}

This example lets dagger know that when we ask for an Engine that it can use a SteamEngine to satisfy that requirement.

Looking back at MainApplication, there is also DaggerTrainApplicationComponent which some may wonder how exactly it came into existence. For that we need a @Component annotated interface.

TrainApplicationComponent.java

@Singleton  
@Component(modules = {AndroidInjectionModule.class, BindingModule.class})  
interface TrainApplicationComponent extends AndroidInjector<MainApplication> {  
}

Lets start from the top, @Singleton while also instructing dagger to only instantiate and use one instance of this also specifies a scope. If anything the component needs is scoped, then so too must the component have a scope.

Next the @Component annotation itself which takes as an argument a list of modules. See how BindingModule, which we defined earlier, is referenced here to tell dagger that this component uses it. To touch on AndroidInjectionModule: this class provides injectors for core android components such as Activity, Fragment, Service, and (you guessed it) Application, along with their support counterparts if they are being used.

Finally we declare our interface and the type of AndroidInjector we wish for it to be, and that’s it! Just like that we have a component to use in our main application.

If you followed along from a fresh project, make sure to declare the application class in your manifest too

AndroidManifest.xml

  <application
      android:name=".MainApplication"
      ...
      >
  </application>

If you get stuck the source code is accessible on my github. The project provides multiple implementations for the abstract classes Engine Fuel and HeatSource. Try editing the BindingModule to change which implementation is used in the application. The app uses the default activity with a plane Hello World! placeholder text, but the values of our train are printed to logcat. The app also injects a train in the application class, and in MainActivity too.

That’s all I’d planned on covering, so until next time, keep on injecting.

Send questions or inquiries to jeff <dot> brandon2010 <at> gmail <dot> com

Written with StackEdit.

Comments

Popular posts from this blog

Will the Real Jeff Brandon Please Stand Up

Story Time: How I became a mentor and why I wish I had started sooner