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
Hidden Power of @Binds
The true power of both @Binds
and static @Provides
is that Dagger doesn’t generate factory classes which wrap those functions. This is the part about generating less code and increasing performance. Not only does it increase performance at build-time, but it also increases the runtime performance because Dagger is no longer allocating extra classes for each component you create.
Finally, by using @Inject
and @Binds
, you’ve abstracted away the creation of the repository dependency. This means that if you change the constructor parameters in the MemoryNewsRepository
, you won’t have to change the provider function. It’ll update automatically, because of the @Inject
.
If you did this for all the dependencies, you could freely update the constructors by adding or removing parameters, changing their order, and you wouldn’t have to do the extra work of updating the provider/factory functions.
The @Component.Builder Interface
Look at the last implementation of the AppModule
. You can see that you needed to do magic with the companion object and static methods because of the use of @Binds
which required the annotated method to be abstract.
But what if you want to provide the instance for the NewsRepository
type when you instantiate the AppComponent
? To do this, change the implementation of the AppModule
class like this:
@Module
class AppModule(
// 1
private val newsRepository: NewsRepository
) {
// 2
@Provides
fun provideNewsListPresenter(): NewsListPresenter = NewsListPresenterImpl(newsRepository)
// 2
@Provides
fun provideNewsDetailPresenter(): NewsDetailPresenter = NewsDetailPresenterImpl(newsRepository)
}
See the following new steps:
- The
AppModule
now defines a constructor with a parameter of the typeNewsRepository
. - The same is then used to create the instances that
@Provides
annotated methods return. The methods don’t have any parameters now.
If you build the application now, you’ll get an error in InitApp
, saying DaggerAppComponent.create()
doesn’t exist.
Providing Parameterized Dependencies
If the @Module
used by a @Component
needs a parameter, Dagger doesn’t generate create()
. It instead generates an implementation of the Builder pattern which requires you to provide the parameterized dependencies. Change the code in the InitApp
like this:
class InitApp : Application() {
lateinit var appComponent: AppComponent
override fun onCreate() {
super.onCreate()
appComponent = DaggerAppComponent
.builder() // 1
.appModule(AppModule(MemoryNewsRepository())) // 2
.build() // 3
}
fun appComp() = appComponent
}
As you can see:
- The
DaggerAppComponent
that Dagger provides now has abuilder()
which returns an implementation of aComponent.Builder
. - You create an instance of the
MemoryNewsRepository
you need, to create the instance of theAppModule
. You then pass to theAppComponent
builder the createdAppModule
usingappModule()
. - The last method to invoke in the Builder implementation is called
build>()
. It returns theAppComponent
implementation.
In this case, you don’t need the @Singleton
annotation anymore. This is because you’re responsible of the creation of the MemoryNewsRepository
class which is then used by the AppComponent
any time it needs it.
Remove @Singleton
from the MemoryNewsRepository
and AppComponent
as in these definitions:
class MemoryNewsRepository : NewsRepository {
- - -
}
@Component(modules = [AppModule::class])
interface AppComponent {
- - -
}
Build and run the app. It now works as expected, reusing the same instance of MemoryNewsRepository
in all the fragments.
On one hand, you own the MemoryNewsRepository
, so passing it in as a parameter didn’t do much. On the other hand, this is how you pass the reference to the Context
, the Resources
, or any other runtime-created dependency you don’t own in Andriod.
Improving Parameterized Dependencies
In the previous code, you created the MemoryNewsRepository
in the InitApp
and passed it to the builder of the AppComponent
encapsulating it within an instance of the AppModule
.
You can do better and pass only what Dagger really needs: The instance of the MemoryNewsRepository
. To do this, change the current code in the AppComponent.kt to get the following implementation:
@Component(modules = [AppModule::class])
interface AppComponent {
fun inject(frag: NewsListFragment)
fun inject(frag: NewsDetailFragment)
// 1
@Component.Builder
interface Builder {
// 2
@BindsInstance
fun repository(repo: NewsRepository): Builder
// 3
fun build(): AppComponent
}
}
There are many important things to note here:
- You created an inner interface annotated with
@Component.Builder
. The name is usuallyBuilder
but it’s not important. - The interface needs to define some operations similar to the ones you created for
@Binds
. Each of them must have a single parameter of the type of the object you need to inject and must have the same interface as return type.
This allows for chaining of such methods. Each of these methods must be annotated with@BindsInstance
. - Finally, need to define a method whose name must be
build
which must have the component as the return type. In this case, the type isAppComponent
.
As you can see, there are many musts you need to follow.
Now you open the AppModule
and remove the parameter from the constructor getting the following implementation:
@Module
class AppModule {
@Provides
fun provideNewsListPresenter(newsRepository: NewsRepository): NewsListPresenter =
NewsListPresenterImpl(newsRepository)
@Provides
fun provideNewsDetailPresenter(newsRepository: NewsRepository): NewsDetailPresenter =
NewsDetailPresenterImpl(newsRepository)
}
You kinda went all the way back to square one. Such a déjà vu. :]
To build and run the app, you need to change the way you create the AppComponent
within InitApp
like this:
class InitApp : Application() {
- - -
override fun onCreate() {
super.onCreate()
appComponent = DaggerAppComponent
.builder()
.repository(MemoryNewsRepository()) // HERE
.build()
}
- - -
}
Dagger created the repository()
for the Builder
which lets you pass the MemoryNewsRepository
directly. Now build and run the project and the app still runs successfully! :]
Making Dagger Work for You Again
The previous implementation for the AppModule
was a déjà vu because it’s very similar to one of the first implementations. But now you also know how to use @Binds
! Change the AppModule
class to this to re-implement @Binds
:
@Module
abstract class AppModule {
@Binds
abstract fun provideNewsListPresenter(newsRepository: NewsListPresenterImpl): NewsListPresenter
@Binds
abstract fun provideNewsDetailPresenter(newsRepository: NewsDetailPresenterImpl): NewsDetailPresenter
}
This isn’t enough. While Dagger knows what classes to use as implementation of the NewsListPresenter
and NewsDetailPresenter
, it doesn’t know how to create an instance of NewsListPresenterImpl
and NewsDetailPresenterImpl
.
You already know the solution: @Inject
. Add the annotation to the constructors like in the following code:
class NewsDetailPresenterImpl @Inject constructor(
private val newsRepository: NewsRepository
) : BasePresenter<NewsModel, NewsDetailView>(),
NewsDetailPresenter {
- - -
}
and
class NewsListPresenterImpl @Inject constructor(
private val newsRepository: NewsRepository
) : BasePresenter<NewsListModel, NewsListView>(),
NewsListPresenter {
- - -
}
Build and run the app as usual.