Koin е DI рамка за Kotlin, която работи без генериране на код, рефлексия и анотации. Библиотеката използва DSL за описване на модули и инжектира зависимости чрез лек контейнер с поддръжка на Android, Ktor и Multiplatform. Според официалната документация на Koin, рамката предоставя модули, скоупове и вградена поддръжка за Jetpack Compose с минимален boilerplate.
Основни точки
Koin — DI рамка за Kotlin, написана на чист език без използване на рефлексия, анотации или генериране на код. За разлика от Dagger Hilt, който изисква процесор за анотации и генериране на код по време на компилация, Koin работи изключително по време на изпълнение, използвайки лек 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 KB, докато Dagger Hilt добавя около 500 KB поради генерирания код.
Контейнерът 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 не проверява графа на зависимости по време на компилация — всички грешки се откриват по време на изпълнение при първия достъп до неразрешена зависимост. Това е компромис, който значително опростява кода и ускорява изграждането, но изисква тестово покритие на DI конфигурацията.
Във версия Koin 3.5 се появи експериментална проверка на графа по време на компилация чрез плъгина Koin Annotations. Основното предимство на Koin — липсата на генериране на код — обаче се губи в този режим, затова повечето екипи продължават да използват класическия DSL подход с проверки по време на изпълнение чрез тестове.
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 или потребителска сесия.
Функцията scope вътре в модул създава скоуп, който живее докато живее свързаният компонент. Всички зависимости, регистрирани в скоупа, се унищожават при затварянето му, предотвратявайки изтичане на памет.
val userScope = module {
scope<UserSession> {
scoped {
UserRepository(get())
}
scoped {
SessionManager(get())
}
}
}
Функцията scoped регистрира зависимост, която ще съществува само вътре в скоупа.
single — регистрация на един екземпляр за цялото приложение с мързелива инициализация. factory — всяко извикване на get() създава нов екземпляр, използва се за ViewModel, хранилища и обекти със състояние.
Интеграцията на Koin в Android проект е минимална: просто добавете зависимостта в build.gradle и извикайте startKoin в Application.onCreate. Koin предоставя модули за интеграция с Jetpack Compose, Navigation и WorkManager.
Специалната библиотека koin-android-compose позволява директно инжектиране на зависимости в Composable функции чрез koinViewModel() и koinInject().
Според Google I/O 2024, Jetpack Compose се превърна в основната рамка за нови Android проекти. Koin предоставя естествена поддръжка на Compose без допълнителна конфигурация.
За тестване Koin предоставя функциите koinTest и koinTestRule, които създават изолиран тестов контейнер с тестови модули. Интеграцията с Jetpack Navigation се осъществява чрез модула koin-androidx-navigation.
За единично тестване на ViewModel с Koin се използва koinTestRule от библиотеката koin-test-junit5 или koin-test-junit4. Реалните зависимости се заменят с мокове чрез MockK.
Една от ключовите възможности на Koin 3.x е поддръжката на Ktor за създаване на сървърни приложения в Kotlin и Compose Multiplatform за десктоп приложения. Това прави Koin единствената DI рамка, покриваща и трите Kotlin платформи без промяна на парадигмата на инжектиране.
За мултимодулни проекти Koin поддържа мързеливо зареждане на модули чрез loadKoinModules, което позволява независимо свързване на DI конфигурацията на всеки feature модул.
Често задавани въпроси
Koin работи по време на изпълнение без генериране на код и анотации, което ускорява изграждането, но не проверява графа на зависимости по време на компилация. Hilt генерира код при компилация и открива DI грешки по-рано, но изисква сложна конфигурация и забавя изграждането.
Да, Koin напълно поддържа Kotlin Multiplatform. Библиотеката koin-core работи на всички Kotlin платформи.
Цикличните зависимости водят до StackOverflowError по време на изпълнение. Koin не ги открива автоматично. Решението е рефакториране на архитектурата.
В Android скоуповете могат да бъдат свързани с жизнения цикъл на Activity или Fragment чрез AndroidScope. При унищожаване на компонента Koin автоматично затваря съответния скоуп.
Използвайте функцията koinTest от модула koin-test. Тя създава изолиран контейнер с тестови модули, който се затваря автоматично след теста.
Обобщение
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също