Koin — що це, принципи Dependency Injection та як працює

Автор: IT Sectr Опубліковано: 2026-05-04 Час читання: 8 хв

Koin — це DI-фреймворк для Kotlin, що працює без кодогенерації, рефлексії та анотацій. Бібліотека використовує DSL для опису модулів і впроваджує залежності через легкий контейнер із підтримкою Android, Ktor і Multiplatform. Згідно з офіційною документацією Koin, фреймворк надає модулі, скопи та вбудовану підтримку Jetpack Compose з мінімальним boilerplate.

Головне

  • Koin — DI-фреймворк для Kotlin, що використовує DSL без рефлексії та кодогенерації.
  • Модуль — логічне групування реєстрацій залежностей через функції single та factory.
  • single — реєстрація синглтона, екземпляр якого створюється один раз.
  • factory — реєстрація, за якої кожен запит створює новий екземпляр.
  • Scope — механізм прив'язки часу життя залежності до компонента, наприклад до Activity.

Що таке Koin і чим він відрізняється

Koin — DI-фреймворк для Kotlin, написаний чистою мовою без використання рефлексії, анотацій або кодогенерації. На відміну від Dagger Hilt, який потребує процесор анотацій та генерацію коду на етапі компіляції, Koin працює виключно в runtime з використанням легкого DSL для опису модулів.

Основна ідея Koin — надати простий API для реєстрації та розв'язання залежностей без необхідності вивчати складні концепції графів залежностей і компонентних дерев. Розробник описує, які класи доступні контейнеру, а Koin автоматично впроваджує їх через конструктор або ліниві делегати by inject. Фреймворк повністю сумісний з Kotlin Multiplatform, що дозволяє використовувати єдиний DI-підхід на Android, iOS та серверній стороні.

За даними опитування Kotlin Developers Community (2025), Koin використовується в 31% комерційних Android-проєктів, поступаючись за популярністю лише Hilt (47%). Основна причина вибору — простота налаштування та відсутність необхідності в кодогенерації, що прискорює збірку проєкту.

Обирайте Koin для середніх і великих проєктів, де важливий швидкий старт розробки, або для Kotlin Multiplatform-рішень, де Hilt недоступний з архітектурних причин.

Koin не використовує рефлексію та кодогенерацію — всі реєстрації будуються на inline-функціях з reified-типами, які на етапі компіляції підставляють конкретний тип у тіло функції. Це робить Koin одним із найлегших DI-фреймворків за розміром підсумкового APK: додавання Koin збільшує розмір додатка лише на 100-150 КБ, тоді як Dagger Hilt додає близько 500 КБ за рахунок згенерованого коду.

Як працюють контейнер і DSL Koin

Контейнер Koin ініціалізується через функцію startKoin, яка приймає лямбду з конфігурацією. Всередині цієї лямбди описуються модулі з реєстраціями — основний будівельний блок DI-логіки.

startKoin та модулі

Функція startKoin створює глобальний контейнер, доступний з будь-якої точки додатка через GlobalContext, однак у багатомодульних проєктах рекомендується використовувати KoinApplication для створення ізольованих контейнерів. В Android для ініціалізації використовується AndroidContext, який автоматично прив'язується до життєвого циклу Application. Модулі реєструються через параметр modules, що приймає список екземплярів Module.

kotlin
val networkModule = module {
    single {
        OkHttpClient()
    }
    single {
        Retrofit.Builder()
            .baseUrl("https://api.example.com")
            .build()
    }
}

startKoin {
    modules(networkModule)
}

Кожен модуль містить визначення через single (синглтон) або factory (новий екземпляр). Визначення можуть посилатися на інші зареєстровані залежності через get(), що формує граф впровадження без явного зазначення типів і без boilerplate-коду.

DSL та inline-функції

Koin активно використовує inline-функції з reified-параметрами для виведення типу з контексту. Це дозволяє писати реєстрації без явного зазначення класу: single { MyService() } автоматично визначає тип за значенням, що повертається лямбдою.

На відміну від Dagger, Koin не перевіряє граф залежностей на етапі компіляції — всі помилки виявляються в runtime при першому зверненні до нерозв'язаної залежності. Це компроміс, який значно спрощує код і прискорює збірку, але потребує тестового покриття DI-конфігурації. Багато команд обирають Koin саме за швидкість розробки та простоту, незважаючи на відсутність compile-time перевірок.

У версії Koin 3.5 з'явилася експериментальна перевірка графа на етапі компіляції через плагін Koin Annotations. Розробник додає анотації @Module та @KoinComponent, а плагін генерує код валідації, який запускається під час збірки. Однак основна перевага Koin — відсутність кодогенерації — в цьому режимі втрачається, тому більшість команд продовжують використовувати класичний DSL-підхід з runtime-перевірками через тести.

Способи впровадження залежностей у Koin

Koin надає кілька способів впровадження залежностей: by inject(), get() та пряма передача через конструктор. Вибір способу залежить від контексту використання.

by inject() — ліниве впровадження

Делегат by inject — найпоширеніший спосіб впровадження у ViewModel та фрагментах Android. Залежність ініціалізується ліниво — тільки при першому зверненні до властивості. Це ефективно для ресурсоємних сервісів, які можуть не знадобитися одразу.

kotlin
class MainViewModel : ViewModel() {
    private val repository: UserRepository by inject()

    fun loadUsers() {
        repository.fetchAll()
    }
}

get() — явне отримання

Функція get повертає екземпляр залежності негайно. Використовується всередині фабричних лямбд під час реєстрації або коли залежність потрібна в синхронному контексті без лінивої ініціалізації. На відміну від by inject(), get() не підтримує ліниве завантаження і потребує, щоб контейнер уже був ініціалізований на момент виклику.

Модулі та скопи в Koin

Scope в Koin — механізм прив'язки часу життя залежностей до конкретного компонента, такого як Activity, Fragment або користувацька сесія. Це ключова функціональність для керування пам'яттю в Android-додатках.

scope — прив'язка до компонента

Функція scope всередині модуля створює скоп, який живе, поки живе пов'язаний компонент. Всі залежності, зареєстровані в скопі, знищуються при його закритті, що запобігає витокам пам'яті.

kotlin
val userScope = module {
    scope<UserSession> {
        scoped {
            UserRepository(get())
        }
        scoped {
            SessionManager(get())
        }
    }
}

Функція scoped реєструє залежність, яка існуватиме лише всередині скопа. При закритті скопа всі scoped-об'єкти стають доступними для збирача сміття.

single vs factory

single — реєстрація єдиного екземпляра на весь додаток з лінивою ініціалізацією. Використовується для сервісів без стану: мережеві клієнти, кеші, логери.

factory — кожен виклик get() створює новий екземпляр. Застосовується для ViewModel, репозиторіїв та об'єктів зі станом, де важливий свіжий екземпляр при кожному зверненні.

Koin в Android проєктах

Інтеграція Koin в Android-проєкт мінімальна: достатньо додати залежність у build.gradle та викликати startKoin в Application.onCreate. Koin надає модулі для інтеграції з Jetpack Compose, Navigation та WorkManager, що робить його повноцінною альтернативою Hilt.

Спеціальна бібліотека koin-android-compose дозволяє впроваджувати залежності безпосередньо в Composable-функції через функції koinViewModel() та koinInject(). Це позбавляє від необхідності передавати контейнер через параметри кожного екрана та робить код ViewModel чистішим завдяки автоматичному зв'язуванню з життєвим циклом.

За даними Google I/O 2024, Jetpack Compose став основним фреймворком для нових Android-проєктів. Koin забезпечує нативну підтримку Compose без додаткових налаштувань, автоматично зв'язуючи скопи з життєвим циклом ViewModel через koinViewModel() з урахуванням coroutine-контексту.

Для тестування Koin надає функції koinTest та koinTestRule, що створюють ізольований тестовий контейнер з тестовими модулями та автоматично закривають його після завершення тесту. Це забезпечує ізоляцію тестів і запобігає витокам станів між тестовими кейсами.

Інтеграція Koin з Jetpack Navigation реалізується через модуль koin-androidx-navigation. ViewModel кожного екрана автоматично отримує залежності через by viewModel() з передачею SavedStateHandle для збереження стану при повороті екрана та відновлення після згортання додатка.

Для модульного тестування ViewModel з Koin використовується koinTestRule з бібліотеки koin-test-junit5 або koin-test-junit4. Правило створює ізольований контейнер з тестовими модулями перед кожним тестом і автоматично закриває його після завершення, запобігаючи витоку станів між тестовими кейсами. Реальні залежності замінюються моками через MockK: модуль з реєстраціями single { mockk() } перевизначає основний модуль, і ViewModel у тесті отримує передбачувану поведінку залежностей.

Однією з ключових можливостей Koin 3.x є підтримка Ktor для створення серверних додатків на Kotlin та Compose Multiplatform для десктопних додатків. Це робить Koin єдиним DI-фреймворком, що покриває всі три платформи Kotlin без зміни парадигми впровадження. Модуль koin-ktor дозволяє реєструвати залежності через install(Koin) в Application-блоці та впроваджувати сервіси в роути через by inject() так само, як в Android. Це робить Koin універсальним DI-рішенням для Kotlin-проєктів будь-якої архітектури — від мобільного клієнта до серверного бекенда.

Інтеграція Koin з Jetpack Navigation через модуль koin-androidx-navigation позбавляє від необхідності вручну створювати ViewModelProvider.Factory для кожного екрана. Для багатомодульних проєктів Koin підтримує ліниве завантаження модулів через loadKoinModules, що дозволяє підключати DI-конфігурацію кожного feature-модуля незалежно.

Часто задавані питання

Чим Koin відрізняється від Dagger Hilt?

Koin працює в runtime без кодогенерації та анотацій, що прискорює збірку, але не перевіряє граф залежностей на етапі компіляції. Hilt генерує код при компіляції та ловить помилки DI раніше, але потребує складного налаштування та сповільнює білд.

Чи підтримує Koin Kotlin Multiplatform?

Так, Koin повністю підтримує Kotlin Multiplatform. Бібліотека koin-core працює на всіх платформах Kotlin, а koin-android та koin-compose додають платформо-специфічні можливості для Android та iOS відповідно.

Як обробляти циклічні залежності в Koin?

Циклічні залежності призводять до StackOverflowError в runtime. Koin не виявляє їх автоматично. Рішення — рефакторинг архітектури: виділення спільного інтерфейсу, використання патерну Listener/Observer або розбиття циклу через фабрику з відкладеною ініціалізацією.

Чи потрібно закривати скопи вручну?

В Android скопи можна прив'язати до життєвого циклу Activity або Fragment через AndroidScope. При знищенні компонента Koin автоматично закриває відповідний скоп. У користувацьких скопах (сесія користувача) закриття виконується вручну викликом scope.close.

Як тестувати код з Koin?

Використовуйте функцію koinTest з модуля koin-test. Вона створює ізольований контейнер з тестовими модулями, який автоматично закривається після тесту. Реальні залежності замінюються моками через модуль з Mockito або MockK.

Підсумки

  • Koin — DI-фреймворк для Kotlin з DSL без рефлексії, анотацій та кодогенерації.
  • Модулі групують реєстрації через single (синглтон) та factory (новий екземпляр).
  • startKoin ініціалізує глобальний контейнер, доступний через by inject() та get().
  • Scope прив'язує час життя залежностей до життєвого циклу Android-компонента.
  • Впровадження в Compose виконується через koinViewModel() та koinInject().
  • Koin підтримує Kotlin Multiplatform, дозволяючи використовувати єдиний DI-підхід на всіх платформах.
  • Помилки DI виявляються в runtime, тому тестове покриття конфігурації є обов'язковим.

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також