Koin — Dependency Injection контейнер для Kotlin

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

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))
    }
}

Преимущества Koin

Рекомендаций