Introducing kotlin-inject-anvil
Posted on 2025-04-03
My team open-sourced the dependency injection framework kotlin-inject-anvil
a few months ago. Dependency injection is a core piece of our and many mobile architectures in general as it removes coupling and
enables easier testing. What I like about kotlin-inject-anvil
in particular is that it’s a
compile time dependency injection framework, meaning if you forget to provide a binding or have duplicate bindings, then you get
an error at build time rather later at runtime in the app. This prevents you from shipping bugs to customers.
kotlin-inject-anvil
removes a lot of the boilerplate you often see with other frameworks.
All you have to do is to declare that a specific implementation should be used for a certain API, inject all required dependencies
and the framework takes care of the rest. It couldn’t be simpler than that:
interface WeatherRepository {
suspend fun provideForcast(): Forecast
}
@Inject
@SingleIn(AppScope::class)
@ContributesBinding(AppScope::class)
class NetworkWeatherRepository(
private val httpClient: HttpClient,
) : WeatherRepository {
override suspend fun provideForecast(): Forecast {
val jsonResponse = httpClient.sendRequest(..)
return Json.decodeFromString<Forecast>(jsonResponse)
}
}
Once you’re accustomed to the annotations, you’ll quickly stop thinking about common questions such as where your dependencies like
the HttpClient
are coming from, how the dependencies are implemented, where and when
they’re instantiated, how to implement singletons, how to cache objects within a certain lifecycle, etc. Need another dependency?
No problem, simply add another constructor parameter. These are problems the framework solves for you.
The best part about kotlin-inject-anvil
is that it was purposefully built for Kotlin
Multiplatform. It allows you to implement common APIs with platform specific dependencies.
No expect
/ actual
declarations nor custom wiring
is needed. All you as consumer have to do is adding annotations to the implementation classes and the framework takes
care of the rest:
// src/commonMain
interface WeatherRepository {
suspend fun provideForcast(): Forecast
}
// src/androidMain
@Inject
@SingleIn(AppScope::class)
@ContributesBinding(AppScope::class)
class AndroidWeatherRepository(
private val application: Application,
) : WeatherRepository { ... }
// src/iosMain
@Inject
@SingleIn(AppScope::class)
@ContributesBinding(AppScope::class)
class IosWeatherRepository(
private val uiApplication: UIApplication,
) : WeatherRepository { ... }
Dependency injection keeps evolving and becomes simpler and more flexible in each iteration. The next step will be
Metro, which combines many of the learnings of previous
frameworks and adds a deeper integration with the Kotlin compiler and IDE. I’ll summarize this evolution and
how kotlin-inject-anvil
helps us in practice at
my KotlinConf 2025 talk.