Dagger 2 Tutorial For Android: Advanced

In this tutorial, you’ll learn about the advanced concepts of Dagger. You’ll learn about component lifecycles, @Binds, and component builders and factories. By Massimo Carli.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 3 of 5 of this article. Click here to view the first page.

Managing the @Component Lifecycle

What does it mean to bind the lifecycle of an object to one of the @Components that manages it? It means that if you want a single instance of the MemoryNewsRepository, you need to have a single instance of the AppComponent, reusing it throughout your app.
The Android solution is to create a custom implementation of the Application and store the component within.

Create a new class named InitApp in rwnews and add the following code:

class InitApp : Application() {

  // 1
  private lateinit var appComponent: AppComponent

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

  // 3
  fun appComp() = appComponent
}

This class which extends the Application. In the code above, it does the following:

  1. Defines the appComponent, containing a reference to the instance which Dagger creates.
  2. Creates the instance of the AppComponent using create() on DaggerAppComponent. Remember that the DaggerAppComponent is an implementation generated by Dagger once you build your project.
  3. Defines appComp() to expose the appComponent.

Next, you need to tell Android to use this definition into the app. Open AndroidManifest.xml and set the .InitApp as the value for the android:name attribute of the application element.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.raywenderlich.rwnews">

  <application
    - - -
    android:name=".init.InitApp"> // HERE
  </application>
  - - -
</manifest>

When you launch the app, Android creates a new instance of the custom Application implementation and invokes onCreate() on it. This creates an instance of the AppComponent and stores it within InitApp.

Now you can proceed to use and share the instance of the component in your target inject classes.

Sharing the @Component

Next, you need to access the same instance of the AppComponent in every place you need it.

Open NewsListFragment.kt and replace the code from onAttach() with the following:

class NewsListFragment : Fragment(), NewsListView {

  @Inject
  lateinit var newsListPresenter: NewsListPresenter
  - - -
  override fun onAttach(context: Context) {
    (context.applicationContext as InitApp) // HERE
      .appComp().inject(this)
    super.onAttach(context)
  }
  - - -
}

Here you’re getting the applicationContext and casting it to InitApp to call appComp() which returns the reference to the single AppComponent within InitApp.

Do the same in NewsDetailFragment.kt:

class NewsDetailFragment : Fragment(), NewsDetailView {
  - - -
  override fun onAttach(context: Context) {
    (context.applicationContext as InitApp)
      .appComp().inject(this)
    super.onAttach(context)
  }
  - - -
}

Now build and run the app and open a news item once or twice. Check the log in output. It should be similar to the following:

I/AdvDagger: In NewsListPresenterImpl using Repository MemoryNewsRepository@82183c0
I/AdvDagger: In NewsDetailPresenterImpl using Repository MemoryNewsRepository@82183c0
I/AdvDagger: In NewsListPresenterImpl using Repository MemoryNewsRepository@82183c0
I/AdvDagger: In NewsDetailPresenterImpl using Repository MemoryNewsRepository@82183c0

The instance of the NewsRepository is always the same. Remember, this isn’t happening because of the @Singleton annotation, but because the instance of the AppComponent you’re using for injection is the same and precisely the one you stored into the InitApp class.

Using @Binds

The current app works, but you can apply optimizations to reduce the code generation time, as well as the quantity of the generated code. You can rely on @Binds instead of @Provides to do this.

Open the AppModule.kt and look at the following definition:

@Module
class AppModule {

  - - -
  @Provides
  @Singleton
  fun provideRepository(): NewsRepository = MemoryNewsRepository()
}

provideRepository() informs Dagger that the implementation to use for the NewsRepository is MemoryNewsRepository. You’re doing some work here because you’re the one that’s creating the instance, and defining the provider contract.

You can avoid this, and delegate the creation of the implementation to Dagger, by using @Binds. It lets you bind a specific interface to the class constructor for the implementation, hence the name.

Delete provideRepository() from the AppModule and create a new file named NewsRepositoryModule.kt within the di package. Then add this code to it:

@Module
abstract class NewsRepositoryModule {

  @Binds
  abstract fun provideRepository(repoImpl: MemoryNewsRepository): NewsRepository
}

This is a new @Module that tells Dagger the class bound to the NewsRepository is MemoryNewsRepository. You do this by defining an abstract method which accepts a single parameter of the implementation type and has the interface type as return type. Because the method is abstract the class is also abstract.

In Kotlin, you’d have to use the @JvmStatic annotation for provide methods into a companion object. You’ll learn how to do this later.

Note: You could put the same abstract method into the AppModule class in place of the one above. In that case, the class must be abstract, and Dagger forces you to make the other provide methods static.

In Kotlin, you’d have to use the @JvmStatic annotation for provide methods into a companion object. You’ll learn how to do this later.

Next, open the AppComponent and add the module to modules in @Component. Its definition becomes the following:

@Component(modules = [AppModule::class, NewsRepositoryModule::class])
@Singleton
interface AppComponent {
  - - -
}

There’s one last step to finish binding the interface to the implementation.

Binding Dependencies

As the last step, you need to remember what you read at the beginning of this tutorial. Dagger needs to know how to create instances.

If you build the app at this point, Dagger will complain that it can’t create an instance of the MemoryNewsRepository because it doesn’t know how to yet. You still need to annotate the MemoryNewsRepository‘ constructor with @Inject. So, open it and add change the class signature to this:

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

You also need to use @Singleton because it’s a property of the specific implementation and not of the NewsRepository abstraction. For this reason, using @Singleton with @Binds is sometimes considered bad practice.

Now build and run the app and check that it works as expected!

RwNews App

User selects a generic article title from list which then displays

RwNews App

Improving Binds

Dagger introduced @Binds as a more compact way of declaring the relation between an abstraction type and its implementation. It lets you delegate the creation of the actual implementation to Dagger as you did with the MemoryNewsRepository. More importantly, it reduces the code generation time along with the number of lines of generated code.

You might argue that you had to create a new class for this as a consequence of the problem of having @Provides methods within an abstract class. This way it seems like you’re adding extra code, to reduce code, which doesn’t make much sense, right? Well, having to declare a small module was simple, and the performance gains behind are worth it, once you start adding more and more dependencies in your project.

But if you still want to avoid having to create a module for @Bind annotated providers, then you can use a different approach, as noted below:

@Module
// 1
abstract class AppModule {

  // 2
  @Module
  companion object {

    // 3
    @JvmStatic
    @Provides 
    fun provideNewsListPresenter(repo: NewsRepository): NewsListPresenter =
      NewsListPresenterImpl(repo)

    // 3
    @JvmStatic
    @Provides 
    fun provideNewsDetailPresenter(repo: NewsRepository): NewsDetailPresenter =
      NewsDetailPresenterImpl(repo)
  }

  // 4
  @Binds
  abstract fun provideRepository(repoImpl: MemoryNewsRepository): NewsRepository
}

Now the AppModule is an abstract class and you had make some relatively big changes:

  1. The AppModule is now abstract.
  2. You need a companion object for the definitions of @Provides. It’s also important to annotate this object with @Module as well.
  3. Providers also annotated with @JvmStatic which is a Kotlin annotation useful if you want to make this function static, in the meaning of Java, in the companion object.
  4. Now you can use @Binds here.

Before building you need to clean what you did previously. Delete NewsRepositoryModule.kt and remove NewsRepositoryModule::class from the AppComponent.

Now, build and run. Check that everything is working as expected.

RwNews App

User selects a generic title from a list and the story displays

RwNews App