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 2 of 5 of this article. Click here to view the first page.

Defining Inject Targets

Now Dagger knows how to create the dependency graph, but it doesn’t know what to do with it. To command Dagger to use the dependencies, you have to define a @Component. Its responsibility is to expose objects you want, from the dependency graph, and to inject those dependencies to target classes.

Dagger supports all three types of injection: Constructor, field and method injection.

You should use constructor injection the most because it allows you to set all the dependencies for an object when you create it. This is the type of injection you used for the NewsDetailPresenterImpl class.

Sometimes this isn’t possible because you don’t have direct control over the creation of the instance of a class. This is the case of classes like Activity and Fragment whose lifecycle is the responsibility of the Android environment. In this case, you use the field injection, which injects dependencies into a class field, like so:

class NewsListFragment : Fragment(), NewsListView {

  // 1
  @Inject
  lateinit var newsListPresenter: NewsListPresenter

  private lateinit var recyclerView: RecyclerView
  private lateinit var newsListViewAdapter: NewsListViewAdapter
  private val newsListModel = NewsListModel(emptyList())
  - - -
}

However, Dagger won’t inject the presenter, even though you’ve annotated it. It forces you to define a @Component and inject to the target class manually. So to continue the setup, you need to add both the module and inject functions to the component, like so:

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

  fun inject(frag: NewsListFragment) // 2

  fun inject(frag: NewsDetailFragment)
}

There are two important things to note here:

  1. Each component can only provide dependencies from the modules assigned to it.
  2. You need to declare each target injection class as a function with the parameter of the type of that class. In this case, the inject() functions represent this, but you can essentially name them whatever you want. This is just the convention.

You can also include modules within other modules, like a composition of functions. Then, once you add one module to a component, you add all those included in that composition. The syntax for doing so would be the following:

class SomeOtherModule

@Module(includes = [SomeOtherModule::class])
class AppModule

Because your project and setup are fairly simple, you don’t need multiple modules, to separate dependencies by their layers or types, however, it’s useful to know how to apply the separation when you encounter more complex projects! Move on to the last step of the setup! :]

Finally, you have to add the following code in one of the lifecycle methods of the target injection classes. For example, you can do so in onAttach(), in fragments:

override fun onAttach(context: Context) {
  // HERE
  DaggerAppComponent.create().inject(this)
  super.onAttach(context)
}

You call inject() on the instance of the AppComponent implementation which Dagger creates for you.

After calling inject(), Dagger guarantees to inject the proper reference for all the fields with @Inject, if they exist in the dependency graph. If they’re missing from the graph, you’ll get a compile-time error telling you which dependency is missing and where.

Scope Management

Open MemoryNewsRepository, which is an implementation of NewsRepository. You’re creating the data for the app within init(). Init should run once per class initialization.

But when, and how many times, does that initialization really happen? Because this is a repository, and you’re using it to fetch data from some entity, it typically should be unique in the app.

Check if the repository is indeed initialized once. Open the NewsListPresenterImpl. There you’ll see a log message as follows:

class NewsListPresenterImpl(
  private val newsRepository: NewsRepository
) : BasePresenter<NewsListModel, NewsListView>(),
  NewsListPresenter {

  override fun displayNewsList() {
    Log.i(TAG, "In NewsListPresenterImpl using Repository $newsRepository") // LOG
    val newsList = newsRepository.list()
    view?.displayNews(NewsListModel(newsList))
  }
}

Run the app, select a news item from the list, go back and then select another news item. Open Logcat and filter the logs using AdvDagger as the tag. Notice a log similar to the following, although you’ve simplified it by removing the full package and time information:

I/AdvDagger: In NewsListPresenterImpl using Repository MemoryNewsRepository@82183c0 // SAME
I/AdvDagger: In NewsDetailPresenterImpl using Repository MemoryNewsRepository@464edaa // DIFFERENT
I/AdvDagger: In NewsListPresenterImpl using Repository MemoryNewsRepository@82183c0 // SAME
I/AdvDagger: In NewsDetailPresenterImpl using Repository MemoryNewsRepository@99c4f98 // DIFFERENT

As you can see, every time Dagger injects a different instance of the NewsRepository. Because of the implementation of the MemoryNewsRepository, this means the initialization happens every time Dagger injects the NewsRepository.

This is something that shouldn’t happen. It adds more overhead to your app’s memory and hinders the performance. You can improve that by making the repository unique.

Providing Singleton Values

You can bind a specific dependency to the lifetime of a component by using a @Scope. The first scope you usually meet when learning about Dagger is @Singleton.

Open the AppModule and update it by adding @Singleton to the the MemoryNewsRepository provider as follows:


// Other imports
import javax.inject.Singleton  // HERE

@Module
class AppModule {

  - - -

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

You’ll also need to import the related package at the beginning of the class. If you try building the app you’ll get the following error:

e: ...AppComponent.java:7: error: [Dagger/IncompatiblyScopedBindings] com.raywenderlich.rwnews.di.AppComponent (unscoped) may not reference scoped bindings:

This happens because you define the NewsRepository implementation in the AppModule class, which contains information that the AppComponent uses to create the instances of its dependency graphs. If you annotate its @Provides method with @Singleton you assign it a scope that the @Component must know to understand when to create its instance.

Head over to the AppComponent and add @Singleton under @Component:

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

  - - -
}

This scopes the repository to the lifetime of AppComponent instances. Build and run, and once again open Logcat. The output is the same as the one before, where each instance has a different @xxxxxx hashcode.

I/AdvDagger: In NewsListPresenterImpl using Repository MemoryNewsRepository@82183c0 // SAME
I/AdvDagger: In NewsDetailPresenterImpl using Repository MemoryNewsRepository@adf2d9b // DIFFERENT
I/AdvDagger: In NewsListPresenterImpl using Repository MemoryNewsRepository@82183c0 // SAME
I/AdvDagger: In NewsDetailPresenterImpl using Repository MemoryNewsRepository@f5e63f1 // DIFFERENT

This happens because even though you’ve marked the @Component and the provider as @Singleton, it doesn’t mean that Dagger will create only one instance of the class.

Note: A Singleton in the Gang Of Four sense is a way to create a single instance of a class which is accessible from any point of an app. You can find more information here.

It’s your job to keep track of each component and its lifecycle. It’s time to learn how to do that.