Dependency Injection in Android with Dagger 2 and Kotlin
In this Android with Kotlin tutorial, you’ll learn about dependency injection and how to make use of the Dagger 2 Java/Android framework for this purpose. By Dario Coletto.
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
Dependency Injection in Android with Dagger 2 and Kotlin
30 mins
- Introduction
- What is Dependency Injection?
- Practical example of Dependency Injection
- The Dependency Inversion Principle
- How can Dagger 2 help with DI
- Getting Started
- MVP
- Dependencies in DroidWiki
- Configure the Project With Dagger 2
- Dagger 2 public APIs
- Module
- @Provides and @Singleton
- Component
- Your first (dependency) injection
- Update HomepageActivity
- The General Pattern
- Injecting the Network Graph
- NetworkModule
- Simplifying API builder
- WikiModule
- Update PresenterModule
- Update WikiApi
- Where to Go From Here?
Component
Now that you have a Dagger module that contains a dependency that can be injected, how do you use it?
That requires the use of another Dagger annotation, @Component
. As you’ve done for the AppModule, create a new Kotlin file in the dagger
package and name it AppComponent
.
Add the following code to the file:
@Singleton
@Component(modules = [AppModule::class])
interface AppComponent
You’ve told Dagger that AppComponent
is a singleton component interface for the app. The @Component
annotation takes a list of modules
as an input. You’re using the literal array syntax available in Kotlin, [AppModule::class]
.
The component is used to connect objects to their dependencies, typically by use of overridden inject()
methods. In order to use the component, it must be accessible from the parts of the app that need injection. Typically, that will happen from the app Application
subclass, as follows.
First, add the following field to WikiApplication
:
lateinit var wikiComponent: AppComponent
Now, you must initialize AppComponent
. Do so by adding the following method to WikiApplication
:
private fun initDagger(app: WikiApplication): AppComponent =
DaggerAppComponent.builder()
.appModule(AppModule(app))
.build()
You’ll likely notice an error called out by Android Studio on DaggerAppComponent
. Click Make Module ‘app’ from the Android Studio Build menu. This will generate a new file, called DaggerAppComponent.java
Import the generated DaggerAppComponent
to clear the compile errors. Ignore the deprecation warning on the appModule()
method; that will be fixed shortly.
Finally, update onCreate()
in WikiApplication
to read as follows:
override fun onCreate() {
super.onCreate()
wikiComponent = initDagger(this)
}
This initializes the wikiComponent
field when the application first starts up.
Your first (dependency) injection
Add the following method declaration within the AppComponent
interface:
fun inject(target: HomepageActivity)
Here, you’ve specified that the HomepageActivity
class will require injection from AppComponent
.
Create a new class in the dagger
package and name it PresenterModule
. Add the following code into PresenterModule
:
@Module
class PresenterModule {
@Provides
@Singleton
fun provideHomepagePresenter(): HomepagePresenter = HomepagePresenterImpl()
}
You’re specifying that a HomepagePresenter
will be provided, and that the presenter returned will be the concrete implementation HomepagePresenterImpl
.
Next, wire up PresenterModule
with AppComponent
by updating the @Component
annotation in AppComponent
to read:
@Component(modules = [AppModule::class, PresenterModule::class])
Update HomepageActivity
Finally, open up the HomepageActivity
class in the ui.homepage
package.
You need to update the presenter
field with the following changes:
- remove the private modifier (Dagger can’t inject private fields)
- add the
@Inject
annotation - add the lateinit modifier
- replace val with var
- remove the initialization
When you’re done, the presenter
declaration looks like this:
@Inject lateinit var presenter: HomepagePresenter
Again, the @Inject
annotation is not part of Dagger; it belongs to javax annotations.
The @Inject
annotation tells Dagger that you want it to do an injection of the presenter
field.
Update onCreate()
by adding the call to AppComponent.inject()
as follows:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_homepage)
(application as WikiApplication).wikiComponent.inject(this)
...
}
You are getting the AppComponent
from WikiApplication
and asking it to inject all known dependencies into HomepageActivity
. Since you annotated presenter
with @Inject
, Dagger will inject a concrete HomepagePresenter
object into HomepageActivity
.
Dagger knows that you defined provideHomepagePresenter()
in the PresenterModule
class, and uses it to create the injected HomepagePresenter
object.
Build and run your app now. It should behave exactly as before, and you’ve avoided the exception. You’ve just finished your first dependency injection with Dagger 2!
The General Pattern
There is a general pattern that emerges based on the previous code changes. Think about the steps just taken to use Dagger dependency injection with HomepageActivity
:
- Add
inject()
inAppComponent
withHomepageActivity
argument. - Add
provideHomepagePresenter()
inPresenterModule
. - Add
@Inject
annotation toHomepagePresenter presenter
inHomepageActivity
. - Add
WikiApplication.wikiComponent.inject(this)
inonCreate()
inHomepageActivity
.
If you consider HomepageActivity
as the target class, and HomepagePresenter
as the source interface to be injected, then the above steps can be generalized as follows for any target class requiring source interfaces to be injected:
- Add
inject()
inAppComponent
withTarget
class argument. - Add
@Provides
annotated method inPresenterModule
for each source object to be injected. - Add
@Inject
annotation to eachSource
member variable inTarget
class. - Add
WikiApplication.wikiComponent.inject(this)
inonCreate()
inTarget
class.
As a challenge, see if you can perform an injection of the EntryPresenter
detail screen into SearchActivity
. This will include removing the following line of code:
private val presenter: EntryPresenter = EntryPresenterImpl()
The steps are just the same as above for HomepageActivity
. Use the pattern, and if you get stuck, check out the final project code at the end of this tutorial.
Injecting the Network Graph
In the app as written currently, both the list screen and detail screen presenters create their own network dependencies. In a typical app that uses Dagger 2 and OkHttp 3 together, OkHttp will be provided by dependency injection.
Here you will see some of the many advantages of using dependency injection and Dagger 2, including:
- Eliminating code duplication.
- Eliminating the need for dependency configuration.
- Automatic construction of a dependency graph.
NetworkModule
Start by creating a new file in the dagger
package, this time named NetworkModule
, which starts off as follows:
@Module
class NetworkModule {
}
What you’re looking to do here is to inject a WikiApi
object into the app’s presenter implementations, so that the presenters can call the API.
For example, if you look at the current HomepagePresenterImpl
, you see that WikiApi
depends on a OkHttpClient
object, and if you need some complex setup for the HTTP client, you will need to configure the OkHttpClient
every time (for example if you want to enable logging with a LoggingInterceptor
).
Moreover the WikiApi
requires 3 Strings that represents:
- the protocol for the request (HTTP or HTTPS)
- the language of Wikipedia you are querying.
- the rest of the base URL for the Wikipedia API (wikipedia.org/w/api.php)
Simplifying API builder
For the sake of simplicity you’re going to merge all these 3 Strings in a single provider.
Start by adding this method into NetworkModule
along with a constant string:
companion object {
private const val NAME_BASE_URL = "NAME_BASE_URL"
}
@Provides
@Named(NAME_BASE_URL)
fun provideBaseUrlString() = "${Const.PROTOCOL}://${Const.LANGUAGE}.${Const.BASE_URL}"
Here you can see the @Named
annotation, that again, is not part of Dagger, but it’s provided by javax.
You are injecting a String
object, and since String
is such a common type to use in an Android app, you’ve taken advantage of the @Named
annotation to specify a specific string to be provided. This same technique can be used for your own types if you need multiple variations injected.
Now that you have the String
representing the base URL, add the following to the bottom of NetworkModule
:
@Provides
@Singleton
fun provideHttpClient() = OkHttpClient()
@Provides
@Singleton
fun provideRequestBuilder(@Named(NAME_BASE_URL) baseUrl: String) =
HttpUrl.parse(baseUrl)?.newBuilder()
@Provides
@Singleton
fun provideWikiApi(client: OkHttpClient, requestBuilder: HttpUrl.Builder?) = WikiApi(client, requestBuilder)
You’re providing an HTTP client, a request builder, and a WikiApi
.