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.

Leave a rating/review
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

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.

Time for injection

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() in AppComponent with HomepageActivity argument.
  • Add provideHomepagePresenter() in PresenterModule.
  • Add @Inject annotation to HomepagePresenter presenter in HomepageActivity.
  • Add WikiApplication.wikiComponent.inject(this) in onCreate() in HomepageActivity.

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() in AppComponent with Target class argument.
  • Add @Provides annotated method in PresenterModule for each source object to be injected.
  • Add @Inject annotation to each Source member variable in Target class.
  • Add WikiApplication.wikiComponent.inject(this) in onCreate() in Target 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.

Dependency Diagram

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.