Dependency Injection in Kotlin: A Complete Guide

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction to Dependency Injection in Kotlin



In modern Kotlin development, especially when creating Android applications, the Dependency Injection (DI) principle has become not just a good practice, but a necessity. Dependency injection allows you to write clean, testable, and scalable code, reducing coupling between system components.



In this article, we will break down what Dependency Injection is, what problems it solves, and how to implement DI in Kotlin — from a manual approach to using popular frameworks such as Hilt and Koin.



What is Dependency Injection?



Dependency Injection is a design pattern in which an object receives its dependencies from the outside, rather than creating them itself. Instead of writing val engine = Engine() inside a class, we "inject" a ready-made instance via a constructor, method, or field.



Let's look at a simple example without DI:



class Car {    private val engine = Engine() // Tight coupling

fun start() { engine.start() }}


Problem: the Car class is tightly coupled to a specific Engine implementation. It is difficult to test and extend.



Now an example with DI via constructor:



class Car(private val engine: Engine) { // Dependency is passed from outside

fun start() { engine.start() }}

// Usageval engine = Engine()val car = Car(engine)car.start()


Now Car does not know how Engine is created. We can easily replace it with a MockEngine for tests or with another implementation.



Why is DI important in Kotlin?



Kotlin, as a modern language, provides excellent capabilities for DI:



  • Null Safety — Kotlin helps avoid NullPointerException when working with dependencies.
  • Concise syntax — constructors with parameters make DI natural.
  • Compatibility with Java — all popular DI frameworks (Dagger, Hilt) support Kotlin.
  • Functional style — Kotlin allows using lambdas and delegates for implementing DI.


Ways to implement DI in Kotlin



1. Manual Injection



The simplest way is to pass dependencies via the constructor. For small projects, this is often sufficient.



class Database(private val connection: Connection)

class UserRepository(private val database: Database)

class UserViewModel(private val userRepository: UserRepository)

// Manual dependency assemblyfun main() { val connection = Connection("postgres://localhost:5432") val database = Database(connection) val repository = UserRepository(database) val viewModel = UserViewModel(repository)}


Disadvantage: as the project grows, manual management becomes cumbersome.



2. Service Locator (Anti-pattern or temporary solution)



Sometimes a Service Locator is used — a central object that stores all dependencies. However, this is considered an anti-pattern because it hides the real dependencies of the class.



object ServiceLocator {    private val database = Database()    private val repository = UserRepository(database)        fun provideViewModel() = UserViewModel(repository)}

// Usageval viewModel = ServiceLocator.provideViewModel()


3. DI frameworks: Hilt and Koin



For serious projects, it is better to use ready-made solutions. Let's look at the two most popular ones for Kotlin/Android.



Hilt (based on Dagger)



Hilt is Google's official DI library for Android. It is based on Dagger, but with a simplified API.



// 1. @HiltAndroidApp annotation in Application@HiltAndroidAppclass MyApplication : Application(                

Blogs

Book Recommendations