Koin — это DI-фреймворк для Kotlin, работающий без кодогенерации, рефлексии и аннотаций. Библиотека использует DSL для описания модулей и внедряет зависимости через лёгкий контейнер с поддержкой Android, Ktor и Multiplatform. По данным официальной документации Koin, фреймворк предоставляет модули, скоупы и встроенную поддержку Jetpack Compose с минимальным boilerplate.
Главное
Koin — DI-фреймворк для Kotlin, написанный на чистом языке без использования рефлексии, аннотаций или кодогенерации. В отличие от Dagger Hilt, требующего процессор аннотаций и генерацию кода на этапе компиляции, Koin работает исключительно в runtime с использованием лёгкого DSL для описания модулей.
Основная идея Koin — предоставить простой API для регистрации и разрешения зависимостей без необходимости изучать сложные концепции графов зависимостей и component-деревьев. Разработчик описывает, какие классы доступны контейнеру, а 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 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также