(Deprecated) Using Dagger in your Android app - Kotlin

1. Introduction

In this codelab you'll learn the importance of Dependency Injection (DI) to create a solid and extensible application that scales to large projects. We'll use Dagger as the DI tool to manage dependencies.

Dependency injection (DI) is a technique widely used in programming and well suited to Android development. By following the principles of DI, you lay the groundwork for a good app architecture.

Implementing dependency injection provides you with the following advantages:

  • Reusability of code.
  • Ease of refactoring.
  • Ease of testing.

If you run into any issues (code bugs, grammatical errors, unclear wording, etc.) as you work through this codelab, please report the issue via the Report a mistake link in the lower left corner of the codelab.

Prerequisites

  • Experience with Kotlin syntax.
  • You understand Dependency Injection and know what the benefits of using Dagger in your Android app are.

What you'll learn

  • How to use Dagger in your Android app at scale.
  • Relevant Dagger concepts to create a more solid and sustainable app.
  • Why you might need Dagger subcomponents and how to use them.
  • How to test your application that uses Dagger with unit and instrumentation tests.

By the end of the codelab, you'll have created and tested an application graph like this:

310186f50792cd08.png

The arrows represent dependencies between objects. This is what we call the application graph: all the classes of the app and the dependencies between them.

Keep reading and learn how to do it!

2. Getting set up

Get the Code

Get the codelab code from GitHub:

$ git clone https://github.com/android/codelab-android-dagger

Alternatively you can download the repository as a Zip file:

Open Android Studio

If you need to download Android Studio, you can do so here.

Project set up

The project is built in multiple GitHub branches:

  • main is the branch you checked out or downloaded. The codelab's starting point.
  • 1_registration_main, 2_subcomponents, and 3_dagger_app are intermediate steps towards the solution.
  • solution contains the solution to this codelab.

We recommend you to follow the codelab step by step at your own pace starting with the main branch.

During the codelab, you'll be presented with snippets of code that you'll have to add to the project. In some places, you'll also have to remove code that will be explicitly mentioned and in comments on the code snippets.

As checkpoints, you have the intermediate branches available in case you need help with a particular step.

To get the solution branch using git, use this command:

$ git clone -b solution https://github.com/android/codelab-android-dagger

Or download the solution code from here:

Frequently asked questions

3. Running the sample app

First, let's see what the starting sample app looks like. Follow these instructions to open the sample app in Android Studio.

  • If you downloaded the zip archive, unzip the file locally.
  • Open the project in Android Studio.
  • Click the execute.png Run button, and either choose an emulator or connect your Android device. The Registration screen should appear.

54d4e2a9bf8177c1.gif

The app consists of 4 different flows (implemented as Activities):

  • Registration: The user can register by introducing username, password and accepting our terms and conditions.
  • Login: The user can log in using the credentials introduced during the registration flow and can also unregister from the app.
  • Home: The user is welcomed and can see how many unread notifications they have.
  • Settings: The user can log out and refresh the number of unread notifications (which produces a random number of notifications).

The project follows a typical MVVM pattern where all the complexity of the View is deferred to a ViewModel. Take a moment to familiarize yourself with the structure of the project.

8ecf1f9088eb2bb6.png

The arrows represent dependencies between objects. This is what we call the application graph: all the classes of the app and the dependencies between them.

The code in the main branch manages dependencies manually. Instead of creating them by hand, we will refactor the app to use Dagger to manage them for us.

Disclaimer

This codelab is not opinionated in the way you architect your app. It's intended to showcase different ways you could plug Dagger into your app architecture: single Activity with multiple fragments (registration and login flows) or multiple Activities (main app flow).

Complete the codelab to understand the main concepts of Dagger so you can apply them to your project accordingly. Some patterns used in this codelab are not the recommended way to build Android applications, however, they're the best ones to explain Dagger.

To learn more about Android app architecture, visit our Guide to App architecture page.

Why Dagger?

If the application gets larger, we will start writing a lot of boilerplate code (e.g. with Factories) which can be error-prone. Doing this wrong can lead to subtle bugs and memory leaks in your app.

In the codelab, we will see how to use Dagger to automate this process and generate the same code you would have written by hand otherwise.

Dagger will be in charge of creating the application graph for us. We'll also use Dagger to perform field injection in our Activities instead of creating the dependencies by hand.

More information about Why Dagger here.

4. Adding Dagger to the project

To add Dagger to your project, open the app/build.gradle file and add the two Dagger dependencies and the kapt plugin to the top of the file.

app/build.gradle

plugins {
   id 'com.android.application'
   id 'kotlin-android'
   id 'kotlin-android-extensions'
   id 'kotlin-kapt'
}

...

dependencies {
    ...
    def dagger_version = "2.40"
    implementation "com.google.dagger:dagger:$dagger_version"
    kapt "com.google.dagger:dagger-compiler:$dagger_version"
}

After adding these lines to the file, click on the "Sync Now" button that appears at the top of the file. That will sync the project and download the new dependencies. We're now ready to use Dagger in the app.

Dagger is implemented using Java's annotations model. It generates code at compile-time using an annotation processor. Annotation processors are supported in Kotlin with the kapt compiler plugin. They are enabled by adding id 'kotlin-kapt' to the top of the file below the id 'kotlin-android-extensions' line.

In the dependencies, the dagger library contains all the annotations you can use in your app and dagger-compiler is the annotation processor that will generate the code for us. The latter will not be packed into your app.

You can find the latest available versions of Dagger here.

5. @Inject annotation

Let's start refactoring the Registration flow to use Dagger.

In order to build the application graph automatically for us, Dagger needs to know how to create instances for the classes in the graph. One way to do this is by annotating the constructor of classes with @Inject. The constructor parameters will be the dependencies of that type.

Open the RegistrationViewModel.kt file and replace the class definition with this one:

RegistrationViewModel.kt

// @Inject tells Dagger how to provide instances of this type
// Dagger also knows that UserManager is a dependency
class RegistrationViewModel @Inject constructor(val userManager: UserManager) {
    ...
}

In Kotlin, to apply an annotation to the constructor, you need to specifically add the keyword constructor and introduce the annotation just before it as shown in the code snippet above.

With the @Inject annotation, Dagger knows:

  1. How to create instances of type RegistrationViewModel.
  2. RegistrationViewModel has UserManager as dependency since the constructor takes an instance of UserManager as an argument.

Dagger doesn't know how to create types of UserManager yet. Follow the same process, and add the @Inject annotation to UserManager 's constructor.

Open the UserManager.kt file and replace the class definition with this one:

UserManager.kt

class UserManager @Inject constructor(private val storage: Storage) {
    ...
}

Now, Dagger knows how to provide instances of RegistrationViewModel and UserManager.

Since UserManager's dependency (i.e. Storage) is an interface, we need to tell Dagger how to create an instance of that in a different way, we'll cover that later.

Views require objects from the graph

Certain Android framework classes such as Activities and Fragments are instantiated by the system so Dagger can't create them for you. For Activities specifically, any initialization code needs to go to the onCreate method. Because of that, we cannot use the @Inject annotation in the constructor of a View class as we did before (that is what is called constructor injection). Instead, we have to use field injection.

Instead of creating the dependencies an Activity requires in the onCreate method as we do with manual dependency injection, we want Dagger to populate those dependencies for us. For field injection (that is commonly used in Activities and Fragments), we annotate with @Inject the fields that we want Dagger to provide.

In our app, RegistrationActivity has a dependency on RegistrationViewModel.

If you open RegistrationActivity.kt, we're creating the ViewModel in the onCreate method just before calling the supportFragmentManager. We don't want to create it by hand, we want Dagger to provide it. For that, we need to:

  1. Annotate the field with @Inject.
  2. Remove its instantiation from the onCreate method.

RegistrationActivity.kt

class RegistrationActivity : AppCompatActivity() {

    // @Inject annotated fields will be provided by Dagger
    @Inject
    lateinit var registrationViewModel: RegistrationViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // Remove following line
        registrationViewModel = RegistrationViewModel((application as MyApplication).userManager)
    }
}

How can we tell Dagger which objects need to be injected into RegistrationActivity? We need to create the Dagger graph (or application graph) and use it to inject objects into the Activity.

6. @Component annotation

We want Dagger to create the graph of dependencies of our project, manage them for us and be able to get dependencies from the graph. To make Dagger do it, we need to create an interface and annotate it with @Component. Dagger will create a Container as we would have done with manual dependency injection.

An interface annotated with @Component will make Dagger generate code with all the dependencies required to satisfy the parameters of the methods it exposes. Inside that interface, we can tell Dagger that RegistrationActivity requests injection.

Create a new package called di under com.example.android.dagger (same level as other packages such as registration). Inside that package, create a new Kotlin file called AppComponent.kt and define the interface as we described above:

app/src/main/java/com/example/android/dagger/di/AppComponent.kt

package com.example.android.dagger.di

import com.example.android.dagger.registration.RegistrationActivity
import dagger.Component

// Definition of a Dagger component
@Component
interface AppComponent {
    // Classes that can be injected by this Component
    fun inject(activity: RegistrationActivity)
}

With the inject(activity: RegistrationActivity)method in the @Component interface, we're telling Dagger that RegistrationActivity requests injection and that it has to provide the dependencies which are annotated with @Inject (i.e. RegistrationViewModel as we defined in the previous step).

Since Dagger has to create an instance of RegistrationViewModel, internally, it also needs to satisfy RegistrationViewModel's dependencies (i.e. UserManager). If during this recursive process of finding dependencies Dagger doesn't know how to provide a particular dependency, it will fail at compile time saying there's a dependency that it cannot satisfy.

Building the app triggers Dagger's annotation processor that will generate the code we need for managing our dependencies. If we do it by using the build button 1d6109b817318da4.png in Android Studio, we get the following error (you might need to enable soft-wrap using this button 25d82e35eef4a435.png to see the error easily):

dagger/app/build/tmp/kapt3/stubs/debug/com/example/android/dagger/di/AppComponent.java:7: error: [Dagger/MissingBinding] com.example.android.dagger.storage.Storage cannot be provided without an @Provides-annotated method

Let's break this error message down. First, it's telling us we're getting an error in AppComponent. The error is of type [Dagger/MissingBinding] which means that Dagger doesn't know how to provide a certain type. If we keep reading, it says that Storage cannot be provided without an @Provides-annotated method.

We haven't told Dagger how to provide an object of type Storage which is needed by UserManager!

7. @Module, @Binds and @BindsInstance annotations

The way we tell Dagger how to provide Storage is different because Storage is an interface and as such cannot be instantiated directly. We need to tell Dagger what implementation of Storage we want to use. In this case it's SharedPreferencesStorage.

To do this we will use a Dagger Module. A Dagger Module is a class that is annotated with @Module.

Similar to Components, Dagger Modules tell Dagger how to provide instances of a certain type. Dependencies are defined using the @Provides and @Binds annotations.

Since this Module will contain information about storage, let's create another file called StorageModule.kt in the same package we created AppComponent.kt. In that file, we define a class called StorageModule and annotate it with @Module.

app/src/main/java/com/example/android/dagger/di/StorageModule.kt

package com.example.android.dagger.di

import dagger.Module

// Tells Dagger this is a Dagger module
@Module
class StorageModule {

}

@Binds annotation

Use @Binds to tell Dagger which implementation it needs to use when providing an interface.

@Binds must annotate an abstract function. The return type of the abstract function is the interface we want to provide an implementation for (i.e. Storage). The implementation is specified by adding a parameter with the interface implementation type (i.e. SharedPreferencesStorage).

StorageModule.kt

// Tells Dagger this is a Dagger module
// Because of @Binds, StorageModule needs to be an abstract class
@Module
abstract class StorageModule {

    // Makes Dagger provide SharedPreferencesStorage when a Storage type is requested
    @Binds
    abstract fun provideStorage(storage: SharedPreferencesStorage): Storage
}

With the code above, we told Dagger "when you need a Storage object use SharedPreferencesStorage"..

Note the following:

  • provideStorage is just an arbitrary method name, it could be anything we like, it doesn't matter to Dagger. What Dagger cares about is the parameter and the return type.
  • StorageModule is abstract now because the provideStorage is abstract.

We've told Dagger that when a Storage object is requested it should create an instance of SharedPreferencesStorage, but we haven't yet told Dagger how

to create instances of SharedPreferencesStorage. We do that the same way as before, by annotating the constructor of SharedPreferencesStorage with @Inject.

SharedPreferencesStorage.kt

// @Inject tells Dagger how to provide instances of this type
class SharedPreferencesStorage @Inject constructor(context: Context) : Storage { ... }

The application graph needs to know about StorageModule. For that, we include it in AppComponent with the modules parameter inside the @Component annotation as follows:

AppComponent.kt

// Definition of a Dagger component that adds info from the StorageModule to the graph
@Component(modules = [StorageModule::class])
interface AppComponent {
    
    // Classes that can be injected by this Component
    fun inject(activity: RegistrationActivity)
}

In this way, AppComponent can access the information that StorageModule contains. In a more complex application, we could also have a NetworkModule that adds information on how to provide an OkHttpClient, or how to configure Gson or Moshi, for example.

If we try to build again we get an error very similar to what we got before! This time, what Dagger doesn't find is: Context.

@BindsInstance annotation

How can we tell Dagger how to provide Context? Context is provided by the Android system and therefore constructed outside of the graph. Since Context is already available at the time we'll be creating an instance of the graph, we can pass it in.

The way to pass it in is with a Component Factory and using the @BindsInstance annotation.

AppComponent.kt

@Component(modules = [StorageModule::class])
interface AppComponent {

    // Factory to create instances of the AppComponent
    @Component.Factory
    interface Factory {
        // With @BindsInstance, the Context passed in will be available in the graph
        fun create(@BindsInstance context: Context): AppComponent
    }

    fun inject(activity: RegistrationActivity)
}

We're declaring an interface annotated with @Component.Factory. Inside, there's a method that returns the component type (i.e. AppComponent) and has a parameter of type Context annotated with @BindsInstance.

@BindsInstance tells Dagger that it needs to add that instance in the graph and whenever Context is required, provide that instance.

Project builds successfully

Your project should now build with no errors.. Dagger has generated the application graph successfully and you're ready to use it.

The implementation of the application graph is automatically generated by the annotation processor. The generated class is called Dagger{ComponentName} and contains the implementation of the graph. We'll use the generated DaggerAppComponent class in the next section.

What does our AppComponent graph look like now?

f7e91e46ef7f2854.png

AppComponent includes StorageModule with information on how to provide Storage instances. Storage has a dependency on Context but since we're providing it when we create the graph, Storage has all its dependencies covered.

The instance of Context is passed in the AppComponent's factory create method. Therefore, we'll have the same instance provided anytime an object needs Context. That's represented with a white dot in the diagram.

Now, RegistrationActivity can access the graph to get objects injected (or populated) by Dagger, in this case RegistrationViewModel (because it is a field which is annotated with @Inject).

As AppComponent needs to populate RegistrationViewModel for the RegistrationActivity, it needs to create an instance of RegistrationViewModel. To do this it needs to satisfy RegistrationViewModel's dependencies (i.e. UserManager) and create an instance of UserManager too. As UserManager has its constructor annotated with @Inject, Dagger will use it to create instances. UserManager has a dependency on Storage but since it's already in the graph, nothing else is needed.

8. Injecting the graph into an Activity

In Android, you usually create a Dagger graph that lives in your Application class because you want the graph to be in memory as long as the app is running. In this way, the graph is attached to the app's lifecycle. In our case, we also want to have the application Context available in the graph. As advantages, the graph is available to other Android framework classes (that can access with their Context) and it's also good for testing since you can use a custom Application class in tests.

Let's add an instance of the graph (i.e. AppComponent) to our custom Application: MyApplication.

MyApplication.kt

open class MyApplication : Application() {

    // Instance of the AppComponent that will be used by all the Activities in the project
    val appComponent: AppComponent by lazy {
        // Creates an instance of AppComponent using its Factory constructor
        // We pass the applicationContext that will be used as Context in the graph
        DaggerAppComponent.factory().create(applicationContext)
    }

    open val userManager by lazy {
        UserManager(SharedPreferencesStorage(this))
    }
}

As we mentioned in the previous section, Dagger generated a class called DaggerAppComponent containing the implementation of the AppComponent graph when we built the project. Since we defined a Component Factory with the @Component.Factory annotation, we can call .factory() that is a static method of DaggerAppComponent. With that, we can now call the create method we defined inside the factory where we pass in the Context, in this case applicationContext.

We do that using a Kotlin lazy initialization so that the variable is immutable and it's only initialized when needed.

We can use this instance of the graph in RegistrationActivity to make Dagger inject the fields annotated with @Inject. How can we do it? We have to call the AppComponent's inject method that takes RegistrationActivity as a parameter.

We also need to remove code which instantiates the fields so we don't overwrite the ones which Dagger now instantiates for us.RegistrationActivity.kt

class RegistrationActivity : AppCompatActivity() {

    // @Inject annotated fields will be provided by Dagger
    @Inject lateinit var registrationViewModel: RegistrationViewModel

    override fun onCreate(savedInstanceState: Bundle?) {

        // Ask Dagger to inject our dependencies
        (application as MyApplication).appComponent.inject(this)

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_registration)

        // REMOVE THIS LINE
        registrationViewModel =