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
Managing the @Component Lifecycle
What does it mean to bind the lifecycle of an object to one of the @Component
s 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:
- Defines the
appComponent
, containing a reference to the instance which Dagger creates. - Creates the instance of the
AppComponent
usingcreate()
onDaggerAppComponent
. Remember that theDaggerAppComponent
is an implementation generated by Dagger once you build your project. - Defines
appComp()
to expose theappComponent
.
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.
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!
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:
- The
AppModule
is nowabstract
. - You need a
companion object
for the definitions of@Provides
. It’s also important to annotate this object with@Module
as well. - Providers also annotated with
@JvmStatic
which is a Kotlin annotation useful if you want to make this functionstatic
, in the meaning of Java, in the companion object. - 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.