Part 1— Simple dependency injection with Dagger

Ian Alexander
ProAndroidDev
Published in
7 min readJul 31, 2019

--

This series takes a basic MVP app using Retrofit and RxJava to display a list of Github repositories; and converts it into a modern Android app — along the way it will give an introduction to a variety of techniques used when architecting Android apps and explain why those techniques are used.

If you just want to see the code go here the repo will be updated as the series progresses.

Part 1—Simple dependency injection with Dagger

Part 2 — Converting Presenters into ViewModels

Part 3 — Single activity architecture

To follow along, this was the initial state of the project. Our first change will be to add dependency injection. By adding DI early, everything else we do can be sped up.

Update 17/06/2020:

With the alpha release of hilt dependency injection with Dagger is about to get a hell of a lot easier! Hilt is an opinionated library over the top of dagger that removes almost all boilerplate dagger code. It makes DI simpler and attempts to standardise how DI is written in Android. From an early look it’s a huge improvement and is sure to become the favoured approach for DI in Android.

The rest of this article goes into some principles about DI, but be aware that with the release of hilt the DI method used in this article should be considered legacy.

Why use DI?

As applications grow, we inevitably have to create abstractions to avoid duplicating code. In object orientated languages like Kotlin, abstractions mean more objects. Those objects need initialised somewhere. So each object may have it’s constructor called several times — or many times.

What happens when you want to add or remove a variable from an objects constructor? You could trundle through a broken build fixing constructor calls, or you could use dependency injection to take care of object creation for you.

Decisions, decisions…

Dependency injection lets you define constructor calls in only one place of your app. Saving you time, and enabling you to write better abstractions. There’s plenty of other benefits to using Dependency Injection, but we’ll leave those to another day.

Choosing a library

At one time Dagger was the go to choice. But since Kotlin came along, several other libraries have sprung up.

Dagger has every possible feature you could ever need for dependency injection, as well as being battle tested and fast. But all this flexibility comes at the price of complexity.

Kotlin alternatives largely sprung up as simple DI frameworks to answer Daggers complexity — by using Kotlin features.

Despite the extra complexity I would always suggest using Dagger. If set up well, it can be as simple — or even simpler — than Kotlin alternatives. Plus, as the libraries been around for a chunk of time, the API is likely to be much more stable in the future.

Using Dagger

Much of the complexity of Dagger comes from changes in the way the library has worked over time and the existence of multiple ways to achieve the same goal. Here we’re going to use a simple approach. You can expand or change this approach to suit your needs, it just provides a starting point with functioning and scalable dependency injection — many projects will never need to go beyond this.

The groundwork

First add these lines to your build.gradle file.

// Adds the core dagger library
implementation "com.google.dagger:dagger:2.24"
kapt "com.google.dagger:dagger-compiler:2.24"

The basic concept of DI is that it provides a central location from which to fetch objects. This is usually referred to as the dependency graph. So lets create that graph.

@Component
interface AndroidPlaygroundComponent {

@Component.Builder
interface Builder {
fun build(): AndroidPlaygroundComponent
}

fun inject(app: App)
}

The core of the graph is the component. We simply define a basic builder interface to construct the component, and define inject functions with any classes we plan to inject this component into.

In the background, Dagger does all the hard work for us. Dagger generates an implementation for this component and handles constructing the graph.

To initialise the dependency graph, all we need to do is call that implementation. As the dependency graph needs to live for the lifetime of the application, we’ll do that in the App class.

class App : Application() {    override fun onCreate() {
super.onCreate()
configureDagger()
}
private fun configureDagger() {
// Daggers generated implementation
// prefixes your component name
// with the prefix Dagger-
DaggerAndroidPlaygroundComponent.builder()
.build()
.inject(this)
}
}

So, now we have our dependency graph. But it doesn’t do very much. Lets make it more useful — by adding and injecting our first object.

Injecting an object

Dagger allows direct annotation of constructors to add objects to the dependency graph — on the inside, dagger will generate a constructor call for us.

class GithubService @Inject constructor() {
// Code emitted for brevity
}

And back in our App class

@Inject lateinit var githubService: GithubService

Congratulations, you just performed dependency injection — and we never even had to call an object constructor! Now GithubService is available to use throughout the App class. What about more complex objects?

Injecting complex objects

Inside GithubService we both create the api and call the api. Lets extract the building of the api to another class.

class GithubApiBuilder @Inject constructor() {

fun buildApi(): GitHubApi {
// Build api
}
}

Leaving us with

class GithubService @Inject constructor(
private val githubApiBuilder: GithubApiBuilder
) {

private val githubApi: GitHubApi = githubApiBuilder.buildApi()

fun fetchRepos(username: String) { // call api }
}

The dependency graph has a reference to both GithubService and GithubApiBuilder and with that knowledge, Dagger is smart enough to construct both objects. We can focus on refactoring a small section of our app — without worrying about how or where it’s used.

We can still only inject objects into the App class. But we really want this behaviour in Activities and Fragments. But first lets simplify our Dagger setup.

Simplifying graph injection

We have no control of creating Android components like Application Activity and Fragment — Android keeps that power for itself. Which presents difficulties when injecting objects from our dependency graph— as we have no control of the constructor in these components.

In our simple example above, this isn’t really a problem, but as we start injecting the graph into other android components we end up having to write a lot of boiler plate code.

To circumvent this problem Dagger provides several libraries that reduce the amount of boiler plate code needed to achieve injection into Android components.

// Adds android specific behaviour
// e.g. for injecting into application, activities, fragments, etc
implementation "com.google.dagger:dagger-android:2.24"
implementation "com.google.dagger:dagger-android-support:2.24"
kapt "com.google.dagger:dagger-android-processor:2.24"

And we can use these libraries in our dependency graph.

@Component(
modules = [
// The android injection modules
// enable hiding boiler plate code
AndroidInjectionModule::class,
AndroidSupportInjectionModule::class
]
)
// Notice the addition of AndroidInjector
// this is used by dagger to help injection into Android components
interface AndroidPlaygroundComponent : AndroidInjector<App> {

@Component.Builder
interface Builder {
fun build(): AndroidPlaygroundComponent
}
// Now we extend AndroidInjector
// there is no need for the inject() function
}

Next we need to be able to inject objects into our Android components. Dagger removes any boiler plate code from this process, and lets us simply extend Dagger wrappers on Android components — simplifying our App class in the process.

class App : DaggerApplication() {

@Inject lateinit var githubService: GithubService

override fun applicationInjector(): AndroidInjector<App> {
return DaggerAndroidPlaygroundComponent.builder()
.build()
}
}

There you go, injection has been simplified…slightly. the real benefit comes when injecting into an Activity or Fragment.

Injecting into Activities

To inject into an Activity, we need to give our dependency graph knowledge of our Activity. Dagger provides a relatively simple way to achieve this through theContributesAndroidInjector annotation.

@Module
interface ActivityModule {

@ContributesAndroidInjector
fun mainActivity(): MainActivity
// Add more activity injectors below
}

This introduces us to the main building block of Dagger — modules. Modules let us make more complex definitions of objects we add to the dependency graph. For cases where annotating a constructor doesn’t cut it.

Our modules can be added to our core component to build up the dependency graph.

@Component(
modules = [
AndroidInjectionModule::class,
AndroidSupportInjectionModule::class,
ActivityModule::class
]
)
interface AndroidPlaygroundComponent : AndroidInjector<App> {
// Code emitted from brevity
}

And in our BaseActivity

abstract class BaseActivity : DaggerAppCompatActivity()

Now we are free to inject as we please into our MainActivity.

class MainActivity : BaseActivity() {

@Inject lateinit var githubService: GithubService

}

There you have it. We can now inject objects into activities. The same pattern can be used for Fragments, Services, and ContentProviders.

And thats all

There you have it. You have dependency injection in your app. All within ~50 lines of code — who said Dagger was complicated?

There are plenty of other features of Dagger and dependency injection that let you use funky architecture patterns and modular design. But for a simple app we don’t need them. We just need a way to create abstractions and add behaviour without getting bogged down in object creation.

Dagger can often seem magical, and that’s testament to how well parts of it are made — which may be a contentious statement. If it’s used well, it removes almost all boiler plate code for object creation from your app.

Later in the series we’ll touch on some more advanced patterns you can achieve with Dagger.

Checkout the app with basic dependency injection integrated here. In part 3 we’ll be mapping our Retrofit layer to domain entities.

A note on architecture

If you’ve checked out the code in the link above, you may notice that all the Dagger code is housed in the di folder. This will also house all future Dagger code.

This is an important concept for architecting apps — the separation of concerns. An app can function with or without dependency injection, so the rest of the app doesn’t need to be aware that dependency injection exits. If the rest of the app knew about dependency injection, it would add to the mental complexity of you or any other person understanding the rest of the app.

In addition to this, the dependency injection library can change over the lifetime of an application (even if that is just a major update to the existing library).

If our DI code is scattered throughout our app, these changes are hard. Limiting our dependency injection code to one place makes those future changes MUCH easier (don’t worry about it future self 🤜 🤛).

The exception here is using @Inject on constructors. Because dogmatically sticking to rules of thumb often costs more time than it saves.

See a problem? Got a comment or a question? stick it in below!

--

--