Android: Dagger 2 Isn’t Hard

Mike Johnston
DVT Software Engineering
7 min readAug 6, 2019
Photo by Yaroslav Кorshikov on Unsplash

During my mission to learn, understand & implement Dagger 2 in Android apps, I’ve come across a surprising amount of confusing articles. I had to spend time and energy just understanding their demo code before I even got to the interesting part. I decided to attempt to present my findings in a (hopefully) simple and straightforward article.

Thanks to Peter-John Welcome for reviewing this article!

But Why?

Dagger 2 is a dependency injection (DI) framework. It helps us by removing a lot of the boilerplate code we usually end up with when implementing DI (such as factory classes and convenience constructors). Dagger will generate this code for us, so we don’t have to see it or maintain it.

Importantly, it is a compile-time DI framework. This means that if we are trying to inject a dependency that isn’t provided anywhere, compilation will fail (rather than throwing a runtime exception). This allows us to catch potential issues earlier along in the development process, ultimately making our app more stable.

Sample Project

Our demo app will simply display a list of Pokémon, as returned by the awesome PokéAPI. My code from the article can be found here. These are the classes of interest:

  • Activity: contains a RecyclerView to display Pokémon dictated by its view model
  • View model: fetches Pokémon from the repository
  • Repository: fetches Pokémon from the service once and stores them in the cache for subsequent calls
  • Service: makes a service call to fetch Pokémon
  • Cache: stores some Pokémon in memory

Our goal is to have Dagger create all these objects for us (except for the activity; the Android system instantiates those). This means we don’t need to have lots of factory classes and convenience constructors polluting the code.

Note: the sample code is written in Kotlin, but everything is also possible in Java.

Dagger Components

Dagger consists of a few key components:

  • @Inject fields: tell Dagger to set these fields for us
  • @Inject constructors: tell Dagger to use this constructor when trying to instantiate the class
  • @Provides & @Binds methods: tell Dagger how to satisfy our dependencies
  • @Module classes: contain the various @Provides & @Binds methods
  • @Component class: binds all @Module classes into a dependency graph

Gradle Dependencies

Firstly, we must add the Dagger dependencies to our project. Let’s add the following dependencies to our app-level build.gradle file (see here for the latest version):

final dagger_version = '2.24'
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"

Note: for non-Kotlin projects, kapt must be replaced with annotationProcessor .

Initial Setup

When first adding Dagger 2 support to the project, there are a few things we’ll need to do:

Custom Application Class

Create a custom application class if there isn’t one already (remember to register it in the manifest):

class PokemonApplication : Application()

Add this field to it:

@Inject
lateinit var androidInjector: DispatchingAndroidInjector<Any>

Make our application class implement HasAndroidInjector and return the field from this method:

class PokemonApplication : Application(), HasAndroidInjector {
// ...
override fun androidInjector() = androidInjector
}

The DispatchingAndroidInjector that we’re injecting into the application class above is just a class that facilitates injecting objects into Android-specific classes (such as activities and fragments). It’s used internally by Dagger whenever we ask it to inject such an Android component, like an activity.

Component Class

Create an interface like this:

@Singleton
@Component(modules = [
// Install the base Android module:
AndroidSupportInjectionModule::class
])
interface AppComponent : AndroidInjector<PokemonApplication>

Important: build the project now. Since Dagger 2 generates code behind the scenes, we’ll need it to do its thing before continuing.

Finally, in our custom application class we must instantiate the generated implementation of AppComponent . The generated class’s name is the interface’s name prefixed with Dagger :

class PokemonApplication : Application(), HasAndroidInjector {
@Inject
lateinit var androidInjector: DispatchingAndroidInjector<Any>

override fun onCreate() {
super.onCreate()
DaggerAppComponent.create()
.inject(this)
}

override fun androidInjector() = androidInjector
}

Notice we’re calling inject(this) in onCreate() which is telling Dagger to inject the current instance of our application class. This results in Dagger setting the androidInjector field to something (doesn’t matter what) because it is annotated with @Inject .

That’s it! Our one-time Dagger 2 setup is complete. Now we can continue with providing the dependencies we care about.

Providing Dependencies

First up, we have to tell Dagger that we want our activity to be injectable. That can be achieved by creating a @Module class with an abstract @ContributesAndroidInjector method:

@Module
abstract class PokemonModule {
@ContributesAndroidInjector
abstract fun contributeViewPokemonActivity(): ViewPokemonActivity
}

Note: the name of the method doesn’t matter, and it is not called at runtime; it’s just for readability. The important part is the return type.

This new module needs to be registered in the Component as well:

@Singleton
@Component(modules = [
AndroidSupportInjectionModule::class,
PokemonModule::class // add our module to the list
])
interface AppComponent : AndroidInjector<PokemonApplication>

Now we can ask Dagger to inject our view model into the activity:

class ViewPokemonActivity : AppCompatActivity() {
@Inject
lateinit var viewModel: ViewPokemonViewModel
override fun onCreate(savedInstanceState: Bundle?) {
// must be called before super.onCreate():
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
// ...
}
}

Try to build the project. It will fail because Dagger doesn’t know how to instantiate the view model yet. This can be rectified by annotating the view model’s constructor with @Inject :

class ViewPokemonViewModel @Inject constructor()

This tells Dagger to use this constructor when it needs to instantiate the class. We should now be able to build and run the project! Although our app isn’t really doing anything yet, we’ll be able to navigate to the activity and the view model will be instantiated by Dagger.

Let’s update our app to actually fetch & display the Pokémon list. To start, we’ll need to get the list of Pokémon from the repository. I’ll add a constructor parameter of type PokemonRepository (which is an interface) to the view model:

class ViewPokemonViewModel @Inject constructor(
private val pokemonRepository: PokemonRepository) {
// Called by the activity to populate the RecyclerView:
fun getPokemon() = pokemonRepository.getPokemon()
}

Notice that we’ve added the repository as a parameter of the constructor that we previously annotated with @Inject . Dagger will try to instantiate each of the constructor parameters when instantiating the class.

Compilation will now fail because Dagger doesn’t know how to instantiate the PokemonRepository to pass to the view model’s constructor. Since it’s an interface we can’t simply add a @Inject -annotated constructor. What we can do, however, is add another abstract method to our PokemonModule which “binds” the repository implementation to the interface:

@Module
abstract class PokemonModule {
// ...
@Binds
abstract fun bindRepo(impl: PokemonRepositoryImpl):
PokemonRepository
}

Again, the method name is not important. The type of the parameter and the return type are what Dagger is looking at. This basically says to Dagger: “whenever someone injects the PokemonRepository interface, go and give them a PokemonRepositoryImpl instance”.

Now we need to add an @Inject -annotated constructor to PokemonRepositoryImpl so Dagger knows how to create it:

class PokemonRepositoryImpl @Inject constructor(
private val service: PokemonService,
private val cache: PokemonCache) : PokemonRepository {
// ...
}

Again, compilation will fail because Dagger doesn’t know how to satisfy the service and cache constructor dependencies. Let’s fix that! Since I’m using Retrofit to make service calls, the construction of a PokemonService object is a bit more complex. This isn’t an issue though, because we can create a concrete @Provides method to construct the Retrofit service:

@Module
abstract class PokemonModule {
// ...
@Module
companion object {
@JvmStatic
@Provides
fun providePokemonService(): PokemonService {
return Retrofit.Builder()
.baseUrl("https://pokeapi.co/api/v2/")
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(PokemonService::class.java)
}
}
}

As usual, the method name doesn’t matter; the return type does. This method will be called by Dagger whenever it needs to create a PokemonService object. Other Dagger-provided dependencies can be added as parameters to @Provides methods.

Finally, we need to specify how the cache object should be created. Since it doesn’t implement an interface, we can create an @Inject -annotated constructor as usual:

class PokemonCache @Inject constructor() {
// ...
}

The app will now compile successfully, but we will run into strange behaviour at runtime. The “bug” is that Dagger will create a new PokemonCache each time it is injected, meaning data is never actually cached in between different activities.

Fortunately, Dagger allows us to annotate the cache class as a singleton, meaning it will reuse the same instance throughout the app:

@Singleton
class PokemonCache @Inject constructor() {
// ...
}

Note: the @Component interface must also be annotated with @Singleton for this to work.

Warning: there can still be multiple instances of the cache if the constructor is called manually.

Warning: make sure you understand scoped bindings before going crazy with the @Singleton annotation.

Now the app will compile & run with no problems, and we’re using Dagger 2 to instantiate our dependencies.

Conclusion

Dependency injection is definitely a must-have in every project; that fact can’t really be argued. I strongly believe that Dagger 2 is worth using for DI in slightly larger projects, because it gets rid of a lot of ugly, boilerplate code. We no longer need to write factory classes that instantiate the required dependencies. Our code can be kept clean, concise and easy to maintain.

--

--