Koin — Dependency Injection контейнер для Kotlin
В современной Kotlin-разработке управление зависимостями является важной задачей. Koin — это легковесный Dependency Injection (DI) контейнер, написанный на Kotlin, который не требует генерации кода, рефлексии или сложной конфигурации. В отличие от Dagger или Spring, Koin использует простой DSL и полностью интегрируется с корутинами и Flow. На этой странице мы разберём, как установить Koin, объявить модули, внедрить зависимости и использовать его в образовательных проектах.
Что такое Dependency Injection?
Dependency Injection (DI) — это паттерн, при котором объект получает свои зависимости извне, а не создаёт их сам. Это упрощает тестирование, делает код более гибким и уменьшает связанность. Koin реализует DI через контейнер, который управляет созданием и жизненным циклом объектов.
Установка Koin
Добавьте следующую зависимость в файл build.gradle.kts:
// build.gradle.kts
dependencies {
implementation("io.insert-koin:koin-core:3.5.3")
// Для Android
implementation("io.insert-koin:koin-android:3.5.3")
}
Основные концепции Koin
- Модуль (module) — блок, в котором объявляются зависимости.
- Определение (definition) — способ создания объекта (single, factory, scoped).
- Контейнер (KoinApplication) — запускает и управляет модулями.
- Внедрение (inject) — получение зависимости через делегаты или функции.
Создание модулей и объявление зависимостей
Koin использует DSL для описания модулей. Рассмотрим пример с репозиторием и сервисом:
// Определяем интерфейс и реализацию
interface UserRepository {
fun getUser(id: Int): String
}
class UserRepositoryImpl : UserRepository {
override fun getUser(id: Int) = "User #$id"
}
class UserService(private val repository: UserRepository) {
fun printUser(id: Int) {
println(repository.getUser(id))
}
}
// Модуль Koin
val appModule = module {
// single — создаёт один экземпляр (синглтон)
single<UserRepository> { UserRepositoryImpl() }
// factory — создаёт новый экземпляр при каждом запросе
factory { UserService(get()) }
}
Запуск контейнера
Перед использованием зависимостей необходимо запустить Koin:
import org.koin.core.context.startKoin
fun main() {
startKoin {
modules(appModule)
}
// Теперь можно получать зависимости
val service: UserService by inject()
service.printUser(42)
}
Способы внедрения зависимостей
- by inject() — ленивое получение (рекомендуется).
- get() — немедленное получение.
- Конструктор — Koin автоматически передаёт зависимости в конструктор.
Пример с использованием by inject():
class MyActivity : AppCompatActivity() {
// Ленивое внедрение
private val repository: UserRepository by inject()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Используем repository
}
}
Scoped зависимости
Иногда необходимо создавать зависимость, которая живёт в рамках определённого контекста (например, сессии пользователя). Для этого используется scope:
val sessionModule = module {
scope<UserSession> {
scoped { UserSessionManager(get()) }
}
}
Работа с корутинами
Koin поддерживает корутины через модуль koin-core-coroutines. Пример асинхронного получения:
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import kotlinx.coroutines.runBlocking
class AsyncService : KoinComponent {
private val repo: UserRepository by inject()
suspend fun loadUser(id: Int): String = repo.getUser(id)
}
fun main() = runBlocking {
startKoin { modules(appModule) }
val service = AsyncService()
val user = service.loadUser(1)
println(user)
}
Тестирование с Koin
Для тестов можно переопределить модули с помощью loadKoinModules или создать изолированный контейнер:
import org.koin.test.KoinTest
import org.koin.test.inject
import org.junit.Test
class UserServiceTest : KoinTest {
private val service: UserService by inject()
@Test
fun testGetUser() {
// Настраиваем тестовый модуль
startKoin {
modules(testModule)
}
assertEquals("User #1", service.getUser(1))
}
}