Koin — це DI-фреймворк для Kotlin, що працює без кодогенерації, рефлексії та анотацій. Бібліотека використовує DSL для опису модулів і впроваджує залежності через легкий контейнер із підтримкою Android, Ktor і Multiplatform. Згідно з офіційною документацією Koin, фреймворк надає модулі, скопи та вбудовану підтримку Jetpack Compose з мінімальним boilerplate.
Головне
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 КБ за рахунок згенерованого коду.
Контейнер Koin ініціалізується через функцію startKoin, яка приймає лямбду з конфігурацією. Всередині цієї лямбди описуються модулі з реєстраціями — основний будівельний блок DI-логіки.
Функція startKoin створює глобальний контейнер, доступний з будь-якої точки додатка через GlobalContext, однак у багатомодульних проєктах рекомендується використовувати KoinApplication для створення ізольованих контейнерів. В Android для ініціалізації використовується AndroidContext, який автоматично прив'язується до життєвого циклу Application. Модулі реєструються через параметр modules, що приймає список екземплярів Module.
val networkModule = module {
single {
OkHttpClient()
}
single {
Retrofit.Builder()
.baseUrl("https://api.example.com")
.build()
}
}
startKoin {
modules(networkModule)
}
Кожен модуль містить визначення через single (синглтон) або factory (новий екземпляр). Визначення можуть посилатися на інші зареєстровані залежності через get(), що формує граф впровадження без явного зазначення типів і без boilerplate-коду.
Koin активно використовує inline-функції з reified-параметрами для виведення типу з контексту. Це дозволяє писати реєстрації без явного зазначення класу: single { MyService() } автоматично визначає тип за значенням, що повертається лямбдою.
На відміну від Dagger, Koin не перевіряє граф залежностей на етапі компіляції — всі помилки виявляються в runtime при першому зверненні до нерозв'язаної залежності. Це компроміс, який значно спрощує код і прискорює збірку, але потребує тестового покриття DI-конфігурації. Багато команд обирають Koin саме за швидкість розробки та простоту, незважаючи на відсутність compile-time перевірок.
У версії Koin 3.5 з'явилася експериментальна перевірка графа на етапі компіляції через плагін Koin Annotations. Розробник додає анотації @Module та @KoinComponent, а плагін генерує код валідації, який запускається під час збірки. Однак основна перевага Koin — відсутність кодогенерації — в цьому режимі втрачається, тому більшість команд продовжують використовувати класичний DSL-підхід з runtime-перевірками через тести.
Koin надає кілька способів впровадження залежностей: by inject(), get() та пряма передача через конструктор. Вибір способу залежить від контексту використання.
Делегат by inject — найпоширеніший спосіб впровадження у ViewModel та фрагментах Android. Залежність ініціалізується ліниво — тільки при першому зверненні до властивості. Це ефективно для ресурсоємних сервісів, які можуть не знадобитися одразу.
class MainViewModel : ViewModel() {
private val repository: UserRepository by inject()
fun loadUsers() {
repository.fetchAll()
}
}
Функція get повертає екземпляр залежності негайно. Використовується всередині фабричних лямбд під час реєстрації або коли залежність потрібна в синхронному контексті без лінивої ініціалізації. На відміну від by inject(), get() не підтримує ліниве завантаження і потребує, щоб контейнер уже був ініціалізований на момент виклику.
Scope в Koin — механізм прив'язки часу життя залежностей до конкретного компонента, такого як Activity, Fragment або користувацька сесія. Це ключова функціональність для керування пам'яттю в Android-додатках.
Функція scope всередині модуля створює скоп, який живе, поки живе пов'язаний компонент. Всі залежності, зареєстровані в скопі, знищуються при його закритті, що запобігає витокам пам'яті.
val userScope = module {
scope<UserSession> {
scoped {
UserRepository(get())
}
scoped {
SessionManager(get())
}
}
}
Функція scoped реєструє залежність, яка існуватиме лише всередині скопа. При закритті скопа всі scoped-об'єкти стають доступними для збирача сміття.
single — реєстрація єдиного екземпляра на весь додаток з лінивою ініціалізацією. Використовується для сервісів без стану: мережеві клієнти, кеші, логери.
factory — кожен виклик get() створює новий екземпляр. Застосовується для ViewModel, репозиторіїв та об'єктів зі станом, де важливий свіжий екземпляр при кожному зверненні.
Інтеграція 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
Однією з ключових можливостей 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 працює в runtime без кодогенерації та анотацій, що прискорює збірку, але не перевіряє граф залежностей на етапі компіляції. Hilt генерує код при компіляції та ловить помилки DI раніше, але потребує складного налаштування та сповільнює білд.
Так, Koin повністю підтримує Kotlin Multiplatform. Бібліотека koin-core працює на всіх платформах Kotlin, а koin-android та koin-compose додають платформо-специфічні можливості для Android та iOS відповідно.
Циклічні залежності призводять до StackOverflowError в runtime. Koin не виявляє їх автоматично. Рішення — рефакторинг архітектури: виділення спільного інтерфейсу, використання патерну Listener/Observer або розбиття циклу через фабрику з відкладеною ініціалізацією.
В Android скопи можна прив'язати до життєвого циклу Activity або Fragment через AndroidScope. При знищенні компонента Koin автоматично закриває відповідний скоп. У користувацьких скопах (сесія користувача) закриття виконується вручну викликом scope.close.
Використовуйте функцію koinTest з модуля koin-test. Вона створює ізольований контейнер з тестовими модулями, який автоматично закривається після тесту. Реальні залежності замінюються моками через модуль з Mockito або MockK.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також