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:

Prerequisites

What you'll learn

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

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!

Get the Code

Get the codelab code from GitHub:

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

Alternatively you can download the repository as a Zip file:

Download Zip

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:

We recommend you to follow the codelab step by step at your own pace starting with the master 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/googlecodelabs/android-dagger

Or download the solution code from here:

Download the final code

Frequently asked questions

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

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

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.

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 master 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 in 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.

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

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'

...

dependencies {
    ...
    def dagger_version = "2.25.2"
    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 apply plugin: 'kotlin-kapt' to the top of the file below the apply plugin: '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.

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 in that RegistrationActivity? We need to create the Dagger graph (or application graph) and use it to inject objects to the Activity.

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.

@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 what the Activity is injecting (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 of the objects it needs to provide Dagger doesn't know how to provide a particular dependency, it will fail at compile time saying there's a dependency that 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 in Android Studio, we get the following error:

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 it 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 forgot to tell Dagger how to provide an object of type Storage!

The way we tell Dagger how to provide Storage is different because Storage is an interface. We need to tell Dagger what implementation of Storage we want to use: SharedPreferencesStorage.

Another way to tell Dagger how to provide instances of a type is with information in Dagger Modules. A Dagger Module is a class that is annotated with @Module. There, you can define how to provide dependencies with the @Provides or @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 annotated 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 (since it's abstract, it doesn't contain any code and the class needs to be abstract too). 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 unique 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 linked the interface Storage with the SharedPreferencesStorage implementation. Notice that StorageModule is abstract now.

Dagger doesn't know how to create instances of SharedPreferencesStorage yet. We do the same as we did before, we annotate 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 a 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 called Factory 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

If you build the project now, you can see how everything is green and no errors are shown. That means that Dagger has generated the graph successfully and you're ready to use it now.

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.

How does our AppComponent graph look like now?

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.

As AppComponent needs to populate RegistrationViewModel for the RegistrationActivity, to be able to create an instance of RegistrationViewModel, it needs to satisfy its 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.

In Android, you usually create a Dagger graph that lives in your Application class because you want an instance of 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 the Context (in this case applicationContext) in.

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 method that takes RegistrationActivity as a parameter.

RegistrationActivity.kt

class RegistrationActivity : AppCompatActivity() {

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

    override fun onCreate(savedInstanceState: Bundle?) {
        // Grabs instance of the application graph
        // and populates @Inject fields with objects from the graph
        (application as MyApplication).appComponent.inject(this)

        super.onCreate(savedInstanceState)
        ...
    }

    ...
}

Calling appComponent.inject(this) populates the fields that RegistrationActivity has annotated with @Inject (i.e. registrationViewModel).

RegistrationActivity is already using Dagger to manage its dependencies! You can go ahead and run the app execute.png.

Did you find a bug? The Main page should appear after the Registration flow! But it doesn't and Login does. Why? The other flows of the app are not using the Dagger graph yet.

MainActivity is still using the userManager defined in MyApplication whereas Registration has used a userManager instance from the Dagger graph.

Let's use Dagger in the Main flow too to fix this issue.

Using Dagger in the Main Flow

As before, we want MainActivity to use Dagger to manage its dependencies. In this case, MainViewModel and UserManager.

To tell Dagger that MainActivity requests injection, we have to add another function with MainActivity as a parameter to the AppComponent's interface.

AppComponent.kt

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

    // Classes that can be injected by this Component
    fun inject(activity: RegistrationActivity)
    fun inject(activity: MainActivity)
}

The name of the functions don't matter (that's why we called both of them inject), what matters is the parameter type. Let's define what we want to inject from Dagger in the MainActivity and inject the graph:

  1. Annotate both fields userManager and mainViewModel with @Inject.

MainActivity.kt

class MainActivity : AppCompatActivity() {

    // @Inject annotated fields will be provided by Dagger
    @Inject
    private lateinit var userManager: UserManager

    @Inject
    private lateinit var mainViewModel: MainViewModel

    ...
}
  1. Delete userManager and mainViewModel initializations since that will be done by Dagger.

MainActivity.kt

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        ...

        // Remove this line
        userManager = (application as MyApplication).userManager
        if (!userManager.isUserLoggedIn()) {
           ...
        } else {
           ...
           // Remove this line too
            mainViewModel = MainViewModel(userManager.userDataRepository!!)
           ...
        }
    }
    ...
}
  1. Inject MainActivity in appComponent to populate the injected fields.

MainActivity.kt

class MainActivity : AppCompatActivity() {
    ...
    override fun onCreate(savedInstanceState: Bundle?) {

        (application as MyApplication).appComponent.inject(this)

        super.onCreate(savedInstanceState)
        ...
    }
}

UserManager is already available in the graph so Dagger knows how to provide it but MainViewModel is not. Let's add the @Inject annotation to its constructor so that Dagger knows how to create instances of the class.

MainViewModel.kt

class MainViewModel @Inject constructor(private val userDataRepository: UserDataRepository) { ... }

Since MainViewModel has a dependency on UserDataRepository, we have to annotate it with @Inject too.

UserDataRepository.kt

class UserDataRepository @Inject constructor(private val userManager: UserManager) { ... }

Since UserManager is already part of the graph, Dagger has all the information it needs to build the graph successfully.

Current state of the application graph at this point

If you try to build the project again, you should get another error.

What does it say? error: Dagger does not support injection into private fields. That's one of the disadvantages of Dagger, injected fields need to have at least visibility package-private or higher. They cannot be private to their own class.

Remove the private modifier from the fields definition.

MainActivity.kt

class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var userManager: UserManager

    @Inject
    lateinit var mainViewModel: MainViewModel

    ...
}

The project can be successfully built now. Let's run execute.png the app again. When you run again, since we registered a user before, you'll be presented with the Login screen. To start fresh as we had run the application for the first time, click on "Unregister" to go to the Registration flow.

When you register, it doesn't go to the Main page! It goes to the Login Activity again. The bug is happening again, but why? Both main and registration flow are getting UserManager injected from the application graph.

The problem is that Dagger always provides a new instance of a type when injecting dependencies by default. How can we make Dagger to reuse the same instance every time? With Scoping.

Sometimes, you might want to provide the same instance of a dependency in a Component for multiple reasons:

  1. You want other types that have this type as dependency to share the same instance (e.g. UserManager in our case).
  2. An object is very expensive to create and you don't want to create a new instance every time it's declared as dependency (e.g. a Json parser).

Use Scopes to have a unique instance of a type in a Component. This is what is also called "to scope a type to the Component's lifecycle". Scoping a type to a Component means that the same instance of that type will be used every time the type needs to be provided.

For AppComponent, we can use the @Singleton scope annotation that is the only scope annotation that comes with the javax.inject package. If we annotate a Component with @Singleton, all the classes also annotated with it will be scoped to the annotated Component.

Open the AppComponent.kt file and annotate the Component with @Singleton.

AppComponent.kt

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

Now, classes annotated with @Singleton will be scoped to AppComponent. Let's annotate UserManager to have a unique instance of it in the application graph.

UserManager.kt

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

Now, the same instance of UserManager will be provided to RegistrationActivity and MainActivity.

Run execute.png the app again and go to the Registration flow to start fresh as we did before. Now, when you complete registration, it goes to the Main flow! Problem solved.

In case you're curious, this is how the application graph looks like now:

Current state of the graph with a unique instance of UserManager in AppComponent

Notice that UserManager is also marked with the white dot. As it happened with Context, the same instance of that UserManager will always be provided when required as dependency in the same instance of AppComponent.

Registration Flow

Let's keep refactoring our app to Dagger. Registration Fragments are still using manual dependency injection, let's migrate those now.

Since we want both Fragments to be injected by Dagger, we need to make Dagger know by adding them to the AppComponent interface:

AppComponent.kt

@Singleton
@Component(modules = [StorageModule::class])
interface AppComponent {
    ...
    fun inject(activity: RegistrationActivity)
    fun inject(fragment: EnterDetailsFragment)
    fun inject(fragment: TermsAndConditionsFragment)
    fun inject(activity: MainActivity)
}

Which fields do we want Dagger to provide? In EnterDetailsFragment, we want Dagger to populate both ViewModels. We do that by annotating the fields with @Inject and removing the private visibility modifier.

EnterDetailsFragment.kt

class EnterDetailsFragment : Fragment() {

    @Inject
    lateinit var registrationViewModel: RegistrationViewModel
    
    @Inject
    lateinit var enterDetailsViewModel: EnterDetailsViewModel

    ...
}

But we also have to remove the manual instantiations we have in the code. Remove the following lines:

EnterDetailsFragment.kt

class EnterDetailsFragment : Fragment() {

    override fun onCreateView(...): View? {
        ...
        // Remove following lines
        registrationViewModel = (activity as RegistrationActivity).registrationViewModel
        enterDetailsViewModel = EnterDetailsViewModel()

        ...
    }
}

Now, we can use the appComponent instance in the Application class to inject the Fragments. For Fragments, we inject Components using the onAttach method after calling super.onAttach.

EnterDetailsFragment.kt

class EnterDetailsFragment : Fragment() {

    override fun onAttach(context: Context) {
        super.onAttach(context)

        (activity!!.application as MyApplication).appComponent.inject(this)
    }
}

What is left for Dagger to know is how to provide instances of EnterDetailsViewModel. We do that by annotating its constructor with @Inject. RegistrationViewModel it's already annotated with @Inject, RegistrationActivity needed it before.

EnterDetailsViewModel.kt

class EnterDetailsViewModel @Inject constructor() { ... }

EnterDetailsFragment is ready, we have to do the same for TermsAndConditionsFragment now:

  1. Annotate with @Inject the fields we want Dagger to provide (i.e. registrationViewModel) and remove the private visibility modifier.
  2. Remove the registrationViewModel instantiation we needed for manual dependency injection.
  3. Inject Dagger in the onAttach method.

TermsAndConditionsFragment.kt

class TermsAndConditionsFragment : Fragment() {

    @Inject
    lateinit var registrationViewModel: RegistrationViewModel

    override fun onAttach(context: Context) {
        super.onAttach(context)

        (activity!!.application as MyApplication).appComponent.inject(this)
    }

    override fun onCreateView(...): View? {
         ...
         // Remove following line
         registrationViewModel = (activity as RegistrationActivity).registrationViewModel
         ...
    }
}

Now you can run execute.png the app.

What happened? It crashed! The problem is that different instances of RegistrationViewModel are being injected in RegistrationActivity, EnterDetailsFragment, and TermsAndConditionsFragment. However, that's not what we want. We want the same instance to be injected for the Activity and Fragments.

What if we annotate RegistrationViewModel with @Singleton? That would solve the problem for now but it will create problems in the future:

We want the registration Fragments to reuse the same ViewModel coming from the Activity, but if the Activity changes, we want a different instance. We need to scope RegistrationViewModel to RegistrationActivity, for that, we can create a new Component for the registration flow and scope the ViewModel to that new registration Component. We use Dagger subcomponents.

Dagger Subcomponents

A RegistrationComponent must be able to access the objects from AppComponent since RegistrationViewModel depends on UserRepository. The way to tell Dagger that we want a new Component to use part of another Component is with Dagger Subcomponents. The new component (i.e. RegistrationComponent) must be a subcomponent of the one containing shared resources (i.e. AppComponent).

Since this is specific to registration, create a new Kotlin RegistrationComponent.kt file inside the registration package. Here you can create a new interface called RegistrationComponent annotated with @Subcomponent that tells Dagger that this is a Subcomponent.

app/src/main/java/com/example/android/dagger/registration/RegistrationComponent.kt

package com.example.android.dagger.registration

import dagger.Subcomponent

// Definition of a Dagger subcomponent
@Subcomponent
interface RegistrationComponent {

}

This Component needs to contain registration specific information. For that, we need to:

  1. Add the inject methods from AppComponent that are specific to Registration, i.e. RegistrationActivity, EnterDetailsFragment, and TermsAndConditionsFragment.
  2. Create a subcomponent Factory that we can use to create instances of this subcomponent.

RegistrationComponent.kt

// Definition of a Dagger subcomponent
@Subcomponent
interface RegistrationComponent {

    // Factory to create instances of RegistrationComponent
    @Subcomponent.Factory
    interface Factory {
        fun create(): RegistrationComponent
    }

    // Classes that can be injected by this Component
    fun inject(activity: RegistrationActivity)
    fun inject(fragment: EnterDetailsFragment)
    fun inject(fragment: TermsAndConditionsFragment)
}

In AppComponent, we have to remove the methods that can inject registration view classes because these won't be used anymore, those classes will use the RegistrationComponent.

Instead, for the RegistrationActivity to create instances of RegistrationComponent, we need to expose its Factory out in the AppComponent interface.

AppComponent.kt

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

    @Component.Factory
    interface Factory {
        fun create(@BindsInstance context: Context): AppComponent
    }

    // Expose RegistrationComponent factory from the graph
    fun registrationComponent(): RegistrationComponent.Factory

    fun inject(activity: MainActivity)
}

We expose the RegistrationComponent factory by declaring a function with that class as return type.

Now, we have to make AppComponent know that RegistrationComponent is its subcomponent so that it can generate code for that. We need to create a Dagger module to do this.

Let's create a file called AppSubcomponents.kt in the di package. In that file, we define a class called AppSubcomponents annotated with @Module. To specify information about subcomponents, we add a list of component class names to the subcomponents variable in the annotation as follows:

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

// This module tells AppComponent which are its subcomponents
@Module(subcomponents = [RegistrationComponent::class])
class AppSubcomponents

This new module also needs to be included in the AppComponent:

AppComponent.kt

@Singleton
@Component(modules = [StorageModule::class, AppSubcomponents::class])
interface AppComponent { ... }

AppComponent is aware RegistrationComponent is its subcomponent.

How does the application graph look like now?

View classes specific to registration get injected by the RegistrationComponent. Since RegistrationViewModel and EnterDetailsViewModel are only requested by classes that use the RegistrationComponent, they're part of it and not part of AppComponent.

We created a subcomponent because we needed to share the same instance of RegistrationViewModel between the Activity and Fragments. As we did before, if we annotate the Component and classes with the same scope annotation, that'll make that type to have a unique instance in the Component.

However, we cannot use @Singleton because it's already been used by AppComponent. We need to create a different one.

In this case, we could call this scope @RegistrationScope but this is not a good practice. The scope annotation's name should not be explicit to the purpose it fulfills. It should be named depending on the lifetime it has since annotations can be reused by siblings Components (e.g. LoginComponent, SettingsComponent, etc). That's why instead of calling it @RegistrationScope, we call it @ActivityScope.

Let's create a file called ActivityScope.kt in the di package and add the definition of ActivityScope as follows:

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

@Scope
@MustBeDocumented
@Retention(value = AnnotationRetention.RUNTIME)
annotation class ActivityScope

To scope RegistrationViewModel to RegistrationComponent, we have to annotate both the class and the interface with @ActivityScope.

RegistrationViewModel.kt

// Scopes ViewModel to components that use @ActivityScope
@ActivityScope
class RegistrationViewModel @Inject constructor(val userManager: UserManager) {
    ...
}

RegistrationComponent.kt

// Scope annotation that the RegistrationComponent uses
// Classes annotated with @ActivityScope will have a unique instance in this Component
@ActivityScope
@Subcomponent
interface RegistrationComponent { ... }

Every time that an instance of RegistrationComponent provides an instance of RegistrationViewModel, it will be the same one.

Subcomponents lifecycle

AppComponent is attached to the lifecycle of the Application because we want to use the same instance of the graph as long as the application is in memory.

What's the lifecycle of RegistrationComponent? One of the reasons why we needed it is because we wanted to share the same instance of the RegistrationViewMode between the registration Activity and Fragments. But also, we want different instances of RegistrationViewModel whenever there's a new registration flow.

RegistrationActivity is the right lifetime for RegistrationComponent: for every new Activity, we'll create a new RegistrationComponent and Fragments that can use that instance of RegistrationComponent.

Since RegistrationComponent is attached to the RegistrationActivity lifecycle, we have to keep a reference to the component in the Activity in the same way we kept the reference to the appComponent in the Application class. In this way, Fragments will be able to access it.

RegistrationActivity.kt

class RegistrationActivity : AppCompatActivity() {

    // Stores an instance of RegistrationComponent so that its Fragments can access it
    lateinit var registrationComponent: RegistrationComponent
    ...
}

We also have to create a new instance of RegistrationComponent in the onCreate method before calling super.onCreate and inject registrationComponent instead of injecting the activity to the appComponent:

  1. Create a new instance of RegistrationComponent by retrieving the factory from the appComponent and calling create. This is possible because we expose the function registrationComponent in the AppComponent interface to return an instance of the RegistrationComponent factory.
  2. Assign that instance to the Activity's registrationComponent variable.
  3. Inject the activity in the recently created registrationComponent to perform field injection and populate the fields annotated with @Inject.

RegistrationActivity.kt

class RegistrationActivity : AppCompatActivity() {
    ...

    override fun onCreate(savedInstanceState: Bundle?) {

        // Remove lines 
        (application as MyApplication).appComponent.inject(this)

        // Add these lines

        // Creates an instance of Registration component by grabbing the factory from the app graph
        registrationComponent = (application as MyApplication).appComponent.registrationComponent().create() 
        // Injects this activity to the just created registration component
        registrationComponent.inject(this)

        super.onCreate(savedInstanceState)
        ...
    }
    ...
}

registrationComponent is available in the RegistrationActivity and we can use that instance to inject the registration fragments. Replace the onAttach method in the Fragments to use the registrationComponent from the Activity:

EnterDetailsFragment.kt

class EnterDetailsFragment : Fragment() {
    ...
    override fun onAttach(context: Context) {
        super.onAttach(context)

        (activity as RegistrationActivity).registrationComponent.inject(this)
    }
    ...
}

And do the same for the TermsAndConditions fragment:

TermsAndConditionsFragment.kt

class TermsAndConditionsFragment : Fragment() {
    ...
    override fun onAttach(context: Context) {
        super.onAttach(context)

        (activity as RegistrationActivity).registrationComponent.inject(this)
    }
}

If you run execute.png the app again and go to the Registration flow to start fresh as we did before, you can see that the registration flow works as expected.

This is how the application graph looks like now:

The difference with the previous diagram is that RegistrationViewModel is scoped to the RegistrationComponent, we represent that with a orange dot on RegistrationViewModel.

Apart from scoping objects to a different lifecycle, creating subcomponents is a good practice to encapsulate different parts of your application from each other.

Structuring your app to create different Dagger subgraphs depending on the flow of your app helps towards a more performing and scalable application in terms of memory and startup time. The opposite of this good practice is having a monolithic Component that knows how to provide every single object of your application, making the Dagger components more difficult to read and modularize.

Let's refactor the Login flow to use Dagger creating another subcomponent for the Login flow.

Create a file called LoginComponent.kt in the login package and add the definition of LoginComponent as we did with RegistrationComponent but this time, with Login-related classes.

app/src/main/java/com/example/android/dagger/login/LoginComponent.kt

// Scope annotation that the LoginComponent uses
// Classes annotated with @ActivityScope will have a unique instance in this Component
@ActivityScope
// Definition of a Dagger subcomponent
@Subcomponent
interface LoginComponent {

    // Factory to create instances of LoginComponent
    @Subcomponent.Factory
    interface Factory {
        fun create(): LoginComponent
    }

    // Classes that can be injected by this Component
    fun inject(activity: LoginActivity)
}

We can annotate the LoginComponent with ActivityScope since the component will have the same lifetime as LoginActivity.

To tell Dagger how to create instances of LoginViewModel, we annotate its constructor with the @Inject annotation.

LoginViewModel.kt

class LoginViewModel @Inject constructor(private val userManager: UserManager) {
    ...
}

In this case, LoginViewModel doesn't need to be reused by other classes, that's why we shouldn't annotate it with @ActivityScope.

We also have to add the new subcomponent to the list of AppComponent's subcomponents in the AppSubcomponents module.

AppSubcomponents.kt

@Module(subcomponents = [RegistrationComponent::class, LoginComponent::class])
class AppSubcomponents

For the LoginActivity to be able to access the LoginComponent factory, we have to expose it in the AppComponent interface.

AppComponent.kt

@Singleton
@Component(modules = [StorageModule::class, AppSubcomponents::class])
interface AppComponent {
    ...
    // Types that can be retrieved from the graph
    fun registrationComponent(): RegistrationComponent.Factory
    fun loginComponent(): LoginComponent.Factory

    // Classes that can be injected by this Component
    fun inject(activity: MainActivity)
}

Everything is ready to create instances of LoginComponent and inject it in the LoginActivity:

  1. Annotate the loginViewModel field with @Inject since we want it to be provided by Dagger and remove the private modifier.
  2. Retrieve the LoginComponent factory from appComponent calling the loginComponent() method, create an instance of LoginComponent with create() and call the inject method of the Component passing the activity in.
  3. Remove the instantiation of loginViewModel from the previous manual dependency injection implementation.

LoginActivity.kt

class LoginActivity : AppCompatActivity() {

    // 1) LoginViewModel is provided by Dagger
    @Inject
    lateinit var loginViewModel: LoginViewModel

    ...

    override fun onCreate(savedInstanceState: Bundle?) {

        // 2) Creates an instance of Login component by grabbing the factory from the app graph
        // and injects this activity to that Component
        (application as MyApplication).appComponent.loginComponent().create().inject(this)

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

        // 3) Remove instantiation
        loginViewModel = LoginViewModel((application as MyApplication).userManager)
        ...
    }
}

If you run execute.png the app again, everything works except Settings. This is because Settings hasn't been refactored to use Dagger and it's using a different instance of UserManager.

With the new login Component, the application graph looks like this:

Let's refactor Settings to use Dagger.

Since we want SettingsActivity fields to be injected by Dagger:

  1. Tell Dagger how to create instances of SettingsActivity dependencies (i.e. SettingsViewModel) by annotating its constructor with @Inject. Dagger already knows how to create instances of SettingsViewModel dependencies.

SettingsViewModel.kt

class SettingsViewModel @Inject constructor(
    private val userDataRepository: UserDataRepository,
    private val userManager: UserManager
) { ... }
  1. Allow SettingsActivity to be injected by Dagger by adding a function that takes SettingsActivity as a parameter in the AppComponent interface.

AppComponent.kt

@Singleton
@Component(modules = [StorageModule::class, AppSubcomponents::class])
interface AppComponent {
    ...
    fun inject(activity: SettingsActivity)
}
  1. In SettingsActivity, annotate injected fields with @Inject and remove the private modifier.
  2. Inject the Activity accessing the appComponent from MyApplication calling inject(this) to populate the fields annotated with @Inject.
  3. Remove the instantiations required by our old implementation of manual dependency injection.

SettingsActivity.kt

class SettingsActivity : AppCompatActivity() {

    // 1) SettingsViewModel is provided by Dagger
    @Inject
    lateinit var settingsViewModel: SettingsViewModel

    override fun onCreate(savedInstanceState: Bundle?) {

        // 2) Injects appComponent
        (application as MyApplication).appComponent.inject(this)

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

        // 3) Remove following lines
        val userManager = (application as MyApplication).userManager
        settingsViewModel = SettingsViewModel(userManager.userDataRepository!!, userManager)
        ...
    }
}

If you run execute.png the app, you can see that the Refresh notifications feature in Settings doesn't work. That's because we're not reusing the same instance of UserDataRepository across MainActivity and SettingsActivity!

Can we scope UserDataRepository to AppComponent by annotating it with @Singleton? Following the same reasoning as before, we don't want to do it because if the user logs out or unregisters, we don't want to keep the same instance of UserDataRepository in memory. That data is specific to a logged in user.

We want to create a Component that lives as long as the user is logged in. All the Activities that can be accessed after the user is logged in will be injected by this component (i.e. MainActivity and SettingsActivity)

Let's create another subcomponent that we can call UserComponent as we did with LoginComponent and RegistrationComponent:

  1. Create a Kotlin file called UserComponent.kt in the user folder.
  2. Create an interface called UserComponent annotated with @Subcomponent that can inject classes that happen after the user is logged in and has a factory.

app/src/main/java/com/example/android/dagger/user/UserComponent.kt

// Definition of a Dagger subcomponent
@Subcomponent
interface UserComponent {

    // Factory to create instances of UserComponent
    @Subcomponent.Factory
    interface Factory {
        fun create(): UserComponent
    }

    // Classes that can be injected by this Component
    fun inject(activity: MainActivity)
    fun inject(activity: SettingsActivity)
}
  1. Add this new subcomponent to the list of AppComponent's subcomponents in the AppSubcomponents.kt file.

AppSubcomponents.kt

@Module(subcomponents = [RegistrationComponent::class, LoginComponent::class, UserComponent::class])
class AppSubcomponents

What is in charge of the lifetime of UserComponent? LoginComponent and RegistrationComponent are managed by its Activities but UserComponent can inject more than one Activity and the number of Activities could potentially increase.

We have to attach the lifetime of this Component to something that knows when the user logs in and out. In our case, UserManager does it. UserManager handles registrations, log in and log out attempts. It makes sense for the UserComponent instance to be there.

If the UserManager needs to create new instances of UserComponent, it needs to access the UserComponent factory. If we add the factory as a constructor parameter, Dagger will provide it when creating the instance of UserManager.

UserManager.kt

@Singleton
class UserManager @Inject constructor(
    private val storage: Storage,
    // Since UserManager will be in charge of managing the UserComponent lifecycle,
    // it needs to know how to create instances of it
    private val userComponentFactory: UserComponent.Factory
) {
    ...
}

In manual dependency injection, we had the user data of the session stored in the UserManager. That decided whether or not the user was logged in. We can do the same with the UserComponent instead.

We can keep an instance of UserComponent in the UserManager to manage the lifetime of it. The user will be logged in if UserComponent is not null. When the user logs out, we can remove the instance of UserComponent. In this way, since UserComponent contains all the data and instances of classes related to a specific user, when the user logs out, when we destroy the component, all the data will be removed from memory.

Modify UserManager to use an instance of UserComponent instead of UserDataRepository:

UserManager.kt

@Singleton
class UserManager @Inject constructor(...) {
    //Remove line
    var userDataRepository: UserDataRepository? = null

    var userComponent: UserComponent? = null
          private set

    fun isUserLoggedIn() = userComponent != null

    fun logout() {
        userComponent = null
    }

    private fun userJustLoggedIn() {
        userComponent = userComponentFactory.create()
    }
}

As you can see in the code above, we create an instance of userComponent when the user logs in using the create method of the UserComponent factory. And we remove the instance when logout() is called.

We want UserDataRepository to be scoped to UserComponent so that both MainActivity and SettingsActivity can share the same instance of it.

Since we've been using the scope annotation @ActivityScope to annotate components that have the Activity managing its lifetime, we need a scope that can cover multiple activities but not all the application, we don't have anything like that yet so we need to create a new scope.

Since this scope covers the lifetime when the user is logged in, we can call it LoggedUserScope.

Create a new Kotlin file called LoggedUserScope.kt in the user package and define the LoggedUserScope scope annotation as follows:

app/src/main/java/com/example/android/dagger/user/LoggedUserScope.kt

@Scope
@MustBeDocumented
@Retention(value = AnnotationRetention.RUNTIME)
annotation class LoggedUserScope

We can annotate both UserComponent and UserDataRepository with this annotation so that UserComponent can always provide the same instance of UserDataRepository.

UserComponent.kt

// Scope annotation that the UserComponent uses
// Classes annotated with @LoggedUserScope will have a unique instance in this Component
@LoggedUserScope
@Subcomponent
interface UserComponent { ... }

UserDataRepository.kt

// This object will have a unique instance in a Component that 
// is annotated with @LoggedUserScope (i.e. only UserComponent in this case).
@LoggedUserScope
class UserDataRepository @Inject constructor(private val userManager: UserManager) {
    ...
}

In MyApplication class, we stored an instance of userManager needed by the manual dependency injection implementation. Since our app is fully refactored to use Dagger, we don't need it anymore. MyApplication looks like this now:

MyApplication.kt

open class MyApplication : Application() {

    val appComponent: AppComponent by lazy {
        DaggerAppComponent.factory().create(applicationContext)
    }
}

We have to modify AppComponent too:

  1. Remove the inject methods since MainActivity and SettingsActivity are not going to be injected by this component anymore, they'll use UserComponent.
  2. Expose UserManager from the graph since MainActivity and SettingsActivity need it to access the instance of UserComponent.

AppComponent.kt

@Singleton
@Component(modules = [StorageModule::class, AppSubcomponents::class])
interface AppComponent {
    ... 
    // 2) Expose UserManager so that MainActivity and SettingsActivity
    // can access a particular instance of UserComponent
    fun userManager(): UserManager

    // 1) Remove following lines
    fun inject(activity: MainActivity)
    fun inject(activity: SettingsActivity)
}

In SettingsActivity, annotate with @Inject the ViewModel (since we want it to be injected by Dagger) and remove the private modifier. To grab the instance of UserComponent that will be initialized because the user is logged in, we call the userManager() method that the appComponent now exposes. Now, we can access the userComponent inside it and inject the Activity.

SettingsActivity.kt

class SettingsActivity : AppCompatActivity() {
    // @Inject annotated fields will be provided by Dagger
    @Inject
    lateinit var settingsViewModel: SettingsViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        // Gets the userManager from the application graph to obtain the instance
        // of UserComponent and gets this Activity injected
        val userManager = (application as MyApplication).appComponent.userManager()
        userManager.userComponent!!.inject(this)

        super.onCreate(savedInstanceState)
        ...
    }
    ...
}

MainActivity does the same thing to inject UserComponent:

  1. UserManager shouldn't be injected anymore since we can grab it from appComponent directly. Remove the userManager field
  2. Create a local variable before checking if the user is logged in or not.
  3. Since the UserComponent will be only available when the user is logged in, we get the userComponent from the userManager and inject the Activity in the else branch.

MainActivity.kt

class MainActivity : AppCompatActivity() {

    // 1) Remove userManager field
    @Inject
    lateinit var userManager: UserManager

    @Inject
    lateinit var mainViewModel: MainViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_settings)

        // 2) Grab userManager from appComponent to check if the user is logged in or not
        val userManager = (application as MyApplication).appComponent.userManager()
        if (!userManager.isUserLoggedIn()) { ... }
        else {
            setContentView(R.layout.activity_main)
            // 3) If the MainActivity needs to be displayed, we get the UserComponent
            // from the application graph and gets this Activity injected
            userManager.userComponent!!.inject(this)
            setupViews()
        }
    }
    ...
}

All screens in our app have been refactored to Dagger! If you run execute.png the app, you can check how everything works as expected now.

After adding UserComponent, the application graph looks like this:

One of the benefits of using dependency injection frameworks like Dagger is that it makes testing your code easier.

Unit tests

You don't have to use Dagger-related code for unit tests. When testing a class that uses constructor injection, you don't need to use Dagger to instantiate that class. You can directly call its constructor passing in fake or mock dependencies directly just as you would if they weren't annotated.

For example, if you take a look at the LoginViewModelTest.kt file that tests LoginViewModel, we're just mocking UserManager and passing it as a parameter as we would've done without Dagger.

LoginViewModelTest.kt

class LoginViewModelTest {
    ...
    private lateinit var viewModel: LoginViewModel
    private lateinit var userManager: UserManager

    @Before
    fun setup() {
        userManager = mock(UserManager::class.java)
        viewModel = LoginViewModel(userManager)
    }

    @Test
    fun `Get username`() {
        whenever(userManager.username).thenReturn("Username")

        val username = viewModel.getUsername()

        assertEquals("Username", username)
    }
    ...
}

All unit tests remain the same as with manual dependency injection except one. When we added the UserComponent.Factory to UserManager, we broke its unit tests. We have to mock what Dagger would return when calling create() on the factory.

Open the UserManagerTest.kt file and create and configure mocks for the UserComponent factory as follows:

UserManagerTest.kt

class UserManagerTest {
    ...

    @Before
    fun setup() {
        // Return mock userComponent when calling the factory
        val userComponentFactory = Mockito.mock(UserComponent.Factory::class.java)
        val userComponent = Mockito.mock(UserComponent::class.java)
        `when`(userComponentFactory.create()).thenReturn(userComponent)

        storage = FakeStorage()
        userManager = UserManager(storage, userComponentFactory)
    }

    ...
}

Now all unit tests should pass.

End-to-end tests

We had our integration tests running without Dagger. As soon as we introduced Dagger in the project and changed the implementation of MyApplication class, we broke them.

Using a custom Application in instrumentation tests

Before that, our end-to-end tests were using a custom application called MyTestApplication. In order to use a different application, we had to create a new TestRunner. The code for that is in app/src/androidTest/java/com/example/android/dagger/MyCustomTestRunner.kt file. The code is already in the project, you don't have to add it.

MyCustomTestRunner.kt

class MyCustomTestRunner : AndroidJUnitRunner() {

    override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application {
        return super.newApplication(cl, MyTestApplication::class.java.name, context)
    }
}

The project knows that this TestRunner needs to be used when running instrumentation tests because it's specified in the app/build.gradle file.

app/build.gradle

...
android {
    ...
    defaultConfig {
        ...
        testInstrumentationRunner "com.example.android.dagger.MyCustomTestRunner"
    }
    ...
}
...

Using Dagger in instrumentation tests

We have to configure MyTestApplication to use Dagger. For integration tests, a good practice is to create a TestApplicationComponent meant for testing. Production and testing use a different component configuration.

What is the difference between our test configuration and our production configuration? Instead of using a SharedPreferencesStorage in UserManager, we want to use a FakeStorage. What's producing SharedPreferencesStorage? StorageModule.

We have to swap the StorageModule for a different one that uses FakeStorage. Since this is only required for instrumentation tests, we create this new class in the androidTest folder. Create a new package called di inside app/src/androidTest/java/com/example/android/dagger/.

There, create a new file called TestStorageModule.kt whose path is app/src/androidTest/java/com/example/android/dagger/di/TestStorageModule.kt.

app/src/androidTest/java/com/example/android/dagger/di/TestStorageModule.kt

// Overrides StorageModule in android tests
@Module
abstract class TestStorageModule {

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

Because of how @Binds works, instead of declaring the method with SharedPreferencesStorage as a parameter, for the TestStorageModule, we pass FakeStorage as parameter. That will make the TestAppComponent that will create next use this implementation of Storage.

Dagger doesn't know how to create instances of FakeStorage, as always, we annotate its constructor with @Inject.

FakeStorage.kt

class FakeStorage @Inject constructor(): Storage { ... }

Now we provide an instance of FakeStorage when Dagger requests a Storage type. Since production and testing use a different component configuration, we have to create another component that acts as our AppComponent. We'll call it TestAppComponent.

Let's create a new Kotlin file in the following path: app/src/androidTest/java/com/example/android/dagger/di/TestAppComponent.kt

app/src/androidTest/java/com/example/android/dagger/di/TestAppComponent.kt

@Singleton
@Component(modules = [TestStorageModule::class, , AppSubcomponents::class])
interface TestAppComponent : AppComponent

We need to specify all the modules in this test Component too. Apart from TestStorageModule, we also have to include the AppSubcomponents module that adds information about its subcomponents. Since we don't need Context for our test graph (the only dependency that required Context before was SharedPreferencesStorage), there's no need to define a Factory for our TestAppComponent.

If you try to build the app, you'll see that Dagger doesn't generate an implementation for TestAppComponent, it should've created a DaggerTestAppComponent class with the test graph. That's because kapt is not acting on the androidTest folder. You have to add the dagger annotation processor artifact to androidTest as follows:

app/build.gradle

...
dependencies {
    ...
    kaptAndroidTest "com.google.dagger:dagger-compiler:$dagger_version"
}

Now if you sync the project and build the app, DaggerTestAppComponent will be available to use. If it doesn't, it's because it's not acting on the androidTest folder yet, try to run the instrumentation tests by right-clicking on the java folder inside the androidTest folder and click on Run 'All Tests'.

We have to make some changes to MyApplication to allow MyTestApplication to create its own Dagger Component.

Extract the appComponent initialization in the by lazy body out to a different method that we can override in the MyTestComponent called initializeComponents() and make it open.

MyApplication.kt

open class MyApplication : Application() {

    val appComponent: AppComponent by lazy {
        initializeComponent()
    }

    open fun initializeComponent(): AppComponent {
        return DaggerAppComponent.factory().create(applicationContext)
    }
}

Now, we can subclass MyApplication and use TestAppComponent in MyTestApplication:

  1. Remove the userManager instance
  2. Override initializeComponent method to make return an instance of DaggerTestAppComponent.

MyTestApplication.kt

class MyTestApplication : MyApplication() {

    override fun initializeComponent(): AppComponent {
        // Creates a new TestAppComponent that injects fakes types
        return DaggerTestAppComponent.create()
    }
}

Tests should pass now. Open the ApplicationTest.kt file in the androidTest/java/com/example/android/dagger folder and click on the execute.png run button next to the class definition. The test should run and pass.

There are other annotations that can be useful in an Android project.

@Provides

Apart from the @Inject and @Binds annotations, you can use @Provides to tell Dagger how to provide an instance of a class inside a Dagger module.

The return type of the @Provides function (that doesn't matter how it's called) tells Dagger what type is added to the graph. The parameters of that function are the dependencies that Dagger needs to satisfy before providing an instance of that type.

In our example, we could've also provided an implementation for the Storage type as follows:

StorageModule.kt

@Module
class StorageModule {

    // @Provides tell Dagger how to create instances of the type that this function 
    // returns (i.e. Storage).
    // Function parameters are the dependencies of this type (i.e. Context).
    @Provides
    fun provideStorage(context: Context): Storage {
        // Whenever Dagger needs to provide an instance of type Storage,
        // this code (the one inside the @Provides method) will be run.
        return SharedPreferencesStorage(context)
    }
}

You can use the @Provides annotation in Dagger modules to tell Dagger how to provide:

Qualifiers

We didn't have to use Dagger qualifiers in our project due to the simplicity of it. Qualifiers are useful when you want to add different implementations of the same type to the Dagger graph. For example, if we wanted different Storage objects to be provided, we could've differentiated them using qualifiers.

For example, if we had SharedPreferencesStorage taking the name of file as parameter:

SharedPreferencesStorage.kt

class SharedPreferencesStorage @Inject constructor(name: String, context: Context) : Storage {

    private val sharedPreferences = context.getSharedPreferences(name, Context.MODE_PRIVATE)

    ...
}

We can add the different implementations with @Provides in StorageModule. We can use the qualifiers to identify a kind of implementation.

StorageModule.kt

@Retention(AnnotationRetention.BINARY)
@Qualifier
annotation class RegistrationStorage

@Retention(AnnotationRetention.BINARY)
@Qualifier
annotation class LoginStorage

@Module
class StorageModule {

    @RegistrationStorage
    @Provides
    fun provideRegistrationStorage(context: Context): Storage {
        return SharedPreferencesStorage("registration", context)
    }

    @LoginStorage
    @Provides
    fun provideLoginStorage(context: Context): Storage {
        return SharedPreferencesStorage("login", context)
    }
}

In the example, we defined two qualifiers: RegistrationStorage and LoginStorage that we can use to annotate @Provides methods. We're adding two types of Storage to the graph: RegistrationStorage and LoginStorage. Both methods return Storage, have the same parameters (dependencies) but a different name. Because the name in @Provides functions don't have any functionality, we have to retrieve them from the graph using the qualifier as follows:

Examples of how to retrieve qualifiers as dependencies

// In a method
class ClassDependingOnStorage(@RegistrationStorage private val storage: Storage) { ... } 

// As an injected field
class ClassDependingOnStorage {

    @Inject
    @field:RegistrationStorage lateinit var storage: Storage
}

You can achieve the same qualifiers' functionality with the @Named annotation, however qualifiers are recommended because:

There's one more part of the codelab app for you to experiment with, and that's refactoring to have a SplashScreen: MainActivity.kt handles the logic of what screen needs to be shown when opening the app. That's problematic because we're doing conditional dependency injection, only injecting when the user will remain on the MainActivity.

These steps don't contain comments or code, so try it on your own:

You're now familiar with Dagger and you should be able to add it to your Android app. In this codelab you learned about: