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 для регистрации и разрешения зависимостей без необходимости изучать сложные концепции графов зависимостей и 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 КБ за счёт сгенерированного кода.

Как работает контейнер и 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 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

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