Dagger 2 Tutorial for Android: Advanced – Part 2

In this tutorial, you’ll learn how to implement advanced features of Dagger 2 by using subcomponents, custom scopes and multibinding. By Massimo Carli.

Leave a rating/review
Download materials
Save for later
Share

As you saw in part one of our Dagger 2 Tutorial For Android: Advanced, Dagger is one of the most-used dependency injection (DI) libraries in Android. It helps you generate code to provide the dependencies for your app. By using it correctly and combining it with your architecture setup, you make your dependency injection clear and distinct – without a lot of work! :]

In the previous tutorial, you tried the main concepts and use cases of Dagger with @Inject, @Module and @Component. You used @Binds and experimented with methods of creating @Component instances using @Component.Builder and @Component.Factory.

Finally, you learned about @Singleton and its relationship with @Components that use it.

As your app grows in size and complexity, you might add multiple @Components. You might also need to separate code into smaller logical groups to reuse across the project by creating different Android Studio modules.

For these cases, Dagger offers powerful tools like @Subcomponent and an interesting feature called multibindings, which allows you to bind the same type, in a map or set of dependencies of that type. This way you can pick and choose which concrete implementation you need. On top of that, performance is always important, so it’s helpful to use different @Scopes.

In this tutorial you’ll:

  • Use @Singleton to define a custom @Scope.
  • Learn how to bind custom @Scopes to dependent @Component instances.
  • Manage dependencies between different @Components through their dependencies attribute.
  • Learn what @Subcomponenta are and how they manage dependencies between @Components with different @Scopes.
  • Find out what multibinding is and how you can use it in your apps.

It also presumes you have knowledge of Dagger 2. If not, check out our Dependency Injection in Android with Dagger 2 and Kotlin and Dagger 2 Tutorial For Android: Advanced – Part One tutorials.

Note: This tutorial assumes you’re familiar with Android development and Android Studio. If these topics are new to you, read the Beginning Android Development and Kotlin for Android tutorials first.

Getting Started

Download and unzip the materials for this tutorial using the Download Materials button at the top or bottom of this page. Open the project using Android Studio 3.5 or greater, then build and run it. You’ll see this:

RwNews App

RwNews App

In this tutorial, you’ll work on RwNews, a basic app that displays a list of news items. The user can select any news item to read its content.

The app is very simple, but its functionality is not as important as its internal workings.

Look at the project structure in Android Studio. You’ll notice a classic MVP architectural pattern implementation with definitions as in the following UML class diagram:

RwNews Architecture

RwNews Architecture

This is the same app from the previous Dagger 2 Tutorial For Android: Advanced tutorial.

Open InitApp.kt and you’ll see the following:

class InitApp : Application() {
  
  lateinit var appComponent: AppComponent

  override fun onCreate() {
    super.onCreate()
    appComponent = DaggerAppComponent
      .factory()
      .repository(MemoryNewsRepository()) // HERE
  }

  fun appComp() = appComponent
} 

Here, you explicitly create a single instance of MemoryNewsRepository. You pass its reference to AppComponent, then share a single instance between all the different Fragment implementations.

But Dagger allows you to get the same result in a better way, without hard-coding dependency creation.

Managing Multiple @Scopes

Dagger doesn’t just generate code, it also allows you to manage dependencies and their lifecycle within the components you define. To do that, it needs to understand how different objects connect and how to provide a way to build the related graph at runtime.

Creating the MemoryNewsRepository instance should be Dagger’s responsibility, as well as creating NewsListPresenterImpl and NewsDetailPresenterImpl.

All objects need memory when you create them, but you don’t always need to create all of the objects at the same time. Your app always needs an instance of MemoryNewsRepository, but you only need an instance of NewsDetailPresenterImpl when you open NewsDetailFragment.

As such, different objects have different lifecycles. This is what @Scope represents.

Exploring @Singleton

Look at the source code for @Singleton:

// 1
package javax.inject;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Scope // 2
@Documented // 3
@Retention(RUNTIME) // 4
public @interface Singleton {} // 5

Note that:

  1. It’s not a Dagger definition. The package javax.inject comes from a the JSR-330 which is the Java Specification Request about annotations.
  2. @Scope allows you to tag this definition as a scope. @Scope is an example of a tagging annotation and is part of the same javax.inject package.
  3. Annotations are usually not included in JavaDocs. @Documented allows you to inform tools that this should be part of the JavaDoc.
  4. @Retention is very important. It informs the compiler of the level of retention or persistence of the annotation during the building process. RUNTIME indicates that the annotation persists up to the execution of the code you annotated.
  5. The @Singleton doesn’t have any attributes, which is common for most scopes.

As you can see in the previous definition, the @Singleton is just a scope-tagged annotation. It’s used to tag other annotations or classes, to add extra scoping behavior.

Using @Singleton

The previous tutorial demonstrated, that @Singleton‘s power transfers to the @Component that uses it. When you define a @Component with a lifecycle of the Application object and annotate it with @Singleton, all the objects within the same dependency graph with that annotation will have the same lifecycle.

To make your app rely on @Singleton, open AppComponent.kt and remove @Component.Factory. Then add @Singleton, ending up with the following implementation:

@Component(modules = [AppModule::class])
@Singleton // HERE
interface AppComponent {

  fun inject(frag: NewsListFragment)

  fun inject(frag: NewsDetailFragment)
}

Now, change the content of the MemoryNewsRepository.kt like so:

@Singleton // HERE
class MemoryNewsRepository @Inject constructor(): NewsRepository {
  - - -
}

Here, you bind the lifecycle of MemoryNewsRepository to the @Component that manages it, using @Singleton.

Then, add the following definition at the end of the AppModule.kt:

@Module
abstract class AppModule {
  - - - 
  @Binds
  abstract fun provideNewsRepository(newsRepository: MemoryNewsRepository): NewsRepository
}

This tells Dagger what implementation of the <code>NewsRepository</code> interface to use.

Finally, insert the Dagger-generated create() into AppInit like this:

class InitApp : Application() {

  lateinit var appComponent: AppComponent

  override fun onCreate() {
    super.onCreate()
    appComponent = DaggerAppComponent.create() // HERE
  }

  fun appComp() = appComponent
}

At this point, Dagger has all the information it needs to create an instance of MemoryNewsRepository. It can use it in every place you need a NewsRepository.

Build and run the app!

RwNews App

RwNews App

Verify you’re using the same instance of MemoryNewsRepository by using the string AdvDagger as a filter in LogCat and checking the output:

I/AdvDagger: In NewsListPresenterImpl using Repository .repository.impl.MemoryNewsRepository@e2aaaab
I/AdvDagger: In NewsDetailPresenterImpl using Repository .repository.impl.MemoryNewsRepository@e2aaaab
I/AdvDagger: In NewsListPresenterImpl using Repository .repository.impl.MemoryNewsRepository@e2aaaab
I/AdvDagger: In NewsDetailPresenterImpl using Repository .repository.impl.MemoryNewsRepository@e2aaaab

Using @Singleton both on the component and the dependency, you’ll reuse the same instance of the dependency as long as the component instance is alive. As long as you initialize the component only once, this will be true, and you’re doing that in InitApp. :]