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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Dagger 2 Tutorial For Android: Advanced
35 mins
- Getting Started
- Taking a Closer Look
- How Can Dagger Help?
- Understanding the Dependency Graph
- What Object To Inject
- Defining Inject Targets
- Scope Management
- Providing Singleton Values
- Managing the @Component Lifecycle
- Sharing the @Component
- Using @Binds
- Binding Dependencies
- Improving Binds
- Hidden Power of @Binds
- The @Component.Builder Interface
- Providing Parameterized Dependencies
- Improving Parameterized Dependencies
- Making Dagger Work for You Again
- Using the @Component.Factory Interface
- Where to Go From Here
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:
- Each component can only provide dependencies from the modules assigned to it.
- 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.
It’s your job to keep track of each component and its lifecycle. It’s time to learn how to do that.