Dependency Injection in Kotlin — How to setup Dagger2

Diogo Peixoto
5 min readJul 25, 2023

--

If you want to use Dagger as your Dependency Injection (DI) framework with Kotlin and don't know how to setup it, you are in the right place.

Configure Kotlin to use Dagger

Dagger is a fully static, compile-time dependency injection framework for Java, Kotlin, and Android. It is an adaptation of an earlier version created by Square and now maintained by Google. [1]

We are going to build an example of digital movies library application that has two main functionalities: find all movies and find movies based on the year. This application will be as simple as possible to present only the Dagger features.

As we can see in the previous diagram, Movie has only two attributes title and year. Our application will have only one service class, MoviesList who has Movies which is an interface that returns all library movies.

We could have had a lot of more implementation of Movies focused on different aspects such as type of movie (gender), storage technology (database, xml file, json file, Restful API, etc) and others. However, for the sake of simplicity I chose to implement based on blockbuster movies.

Before jumping in the configuration, let's highlight the technologies used in our example:

  1. Kotlin 1.8 (JVM 19)
  2. Dagger 2.46.1
  3. Gradle 8.0.2 (Kotlin DSL)

Add Dependencies

To use Dagger library, we need to add it in dependencies on our build file build.gradle.tks.

dependencies {
implementation("com.google.dagger:dagger:2.46.1")

kapt("com.google.dagger:dagger-compiler:2.46.1")
}

As Dagger is a fully-static and compile-time dependency injection framework, it relies on annotations to generate code that will be used in our application — this process is called annotation processor.

So, we need to setup our annotation processor. Kotlin has two plugins: Kotlin Symbol Processing (KSP) and Kotlin Annotation Processing Tool (KAPT). Kapt is in maintenance mode and documentation recommends to use KSP. However, Dagger does not support KSP yet [2]. Then, we have to configure Kapt to generate code in compile-time.

Create Movie data class and Movies interface

Our Movie class is as simple as possible and has only two attributes: title and year. Meanwhile our interface repository has only one method that returns all movies of our library.

data class Movie(val title: String, val year: Number)

interface Movies {
fun findAll(): List<Movie>
}

Implement Movies interface

Our Movies implementation will return only blockbuster movies. As the interface will be provided by Dagger, we need to tell it how can it constructs the MoviesBlockbuster implementation.

import javax.inject.Inject

class MoviesBlockbuster @Inject constructor(): Movies {
override fun findAll() = listOf(
Movie("John Wick 4: Baba Yaga", 2023),
Movie("KSI: In Real Life", 2023),
Movie("Assassin", 2023),
Movie("The Gray Man", 2022),
Movie("The Whale", 2022),
Movie("Hustle", 2022)
)
}

In order to let Dagger knows that it will construct MoviesBlockbuster, we need to add @Inject annotation. Othterwise, Kapt will throw a compile-time error while trying to instantiate an implementation of Movies.

Create MoviesList service class

Our MoviesList service class has two functionalities: the first one (findAll) returns all movies of our library. To do so, service class calls Movies to return all movies from its library. In this method MoviesList acts just as a proxy.

The second one (findByYear) returns movies from a specific year. In order to do, service class calls Movies and performs filtering logic — to get only movies from a specific year — on the result of Movies call.

import javax.inject.Inject

class MoviesList @Inject constructor (private val movies: Movies){
fun findAll() = movies.findAll()

fun findByYear(year: Number) = movies.findAll().filter { it.year == year }
}

However, it is not a concern or responsibility of MoviesList to know how to instantiate its Movies dependency. Dagger, as a DI framework, is the one responsible to do it. So we also need to add @Inject annotation to let Dagger instantiate MoviesList service.

Create Movies Module

Now, we are missing two pieces of our application: tells Dagger how to instantiate Movies and create a class that provides MoviesList service class. In this section, we will will see the first missing part.

In the previous sections, we created our Movies interface, created MoviesBlockbuster implementation and created our MoviesList service class.

MoviesList receives Movies in its constructor, so we need to tell Dagger how to bind the interface with the implementation. In order to do that, we need to create a Dagger module and a method that returns the interface implementation.

As your Movies implementation is quite simple and doesn't receive any parameters, we can use @Bind in favor of @Provider . To be able to use @Bind the dependency needs to meet following criteria:

  • @Binds needs to have just one method parameter which is the return type of the method or its subtype. To check more about differences between @Binds and @Provider take a look at Dagger Documentation.
import dagger.Binds
import dagger.Module
import javax.inject.Singleton

@Module
interface MoviesModule {

@Binds
@Singleton
fun bindMovies(moviesBlockbuster: MoviesBlockbuster): Movies
}

The snippet code above shows how to create a Dagger module. The name of the method could be any name. However, by Dagger's convention, we use bind as prefix plus the name of the component that Dagger constructs.

Create MoviesApp

After telling Dagger how to create our dependencies, finally, we need to create an application that will provide our MoviesList functionality.

import dagger.Component
import javax.inject.Singleton

@Singleton
@Component(modules = [MoviesModule::class])
interface MoviesApp {
fun moviesList(): MoviesList
}

The snippet above, declares our MoviesApp as an interface, annotated by @Singleton and @Component . The first one tells Dagger that it will only have one instance of MoviesApp . The second one, annotates an interface or abstract class for which a fully-formed, dependency-injected implementation is to be generated by Dagger from a set of modules[3].

As Dagger is a fully static, compile-time dependency injection framework when we finish to setup, we need to run kaptKotlin annotation processor task to generate Dagger classes. After running it, we can use MoviesApp generated class.

fun main() {
val app = DaggerMoviesApp.builder().build()
val movies = app.moviesList()

movies.findByYear(2022).forEach { println(it) }
movies.findByYear(2023).forEach { println(it) }
}

Troubleshooting

If we don’t add @inject to classes that will have dependencies injected by Dagger we can a missing binding usage problem:

> Task :kaptKotlin FAILED
/build/tmp/kapt3/stubs/main/MoviesApp.java:6: error: [Dagger/MissingBinding] MoviesBlockbuster cannot be provided without an @Inject constructor or an @Provides-annotated method.
public abstract interface MoviesApp {
^
Missing binding usage:
MoviesBlockbuster is injected at
MoviesModule.bindMovies(moviesBlockbuster)
Movies is injected at
MoviesList(movies)
MoviesList is requested at
MoviesApp.moviesList()

In the error above, we forgot to add @inject to MoviesBlockbuster constructor. To fix this problem, we need to add it and re-run kaptKotlin task.

With just a few steps we have configured Dagger to be our DI framework and created a simple application that uses Dagger generated ccode class. If you liked this article and want to dive deep in Dagger's story, I would recommend you to watch "DAGGER 2 — A New Type of dependency injection" talk [4].

References

  1. https://dagger.dev/
  2. https://github.com/google/dagger/issues/2349
  3. https://dagger.dev/api/2.28/dagger/Component.html
  4. https://www.youtube.com/watch?v=oK_XtfXPkqw

--

--

Diogo Peixoto

Apaixonado por compartilhar, errar, aprender e um pouco de engenharia de software