Koin: внедрение зависимостей в KMP
Кто создаёт репозитории, API и базу — и как собрать это в один граф, общий для двух платформ.
Koin — мультиплатформенный DI-фреймворк, описывающий граф зависимостей в виде модулей (Kotlin DSL) и предоставляющий объекты по запросу без кодогенерации.
Зачем DI в общем модуле
Когда классов становится много (API, репозитории, база, settings, ViewModel), кто-то должен их создавать и связывать. Делать это руками в каждой точке — больно и не масштабируется. DI-контейнер описывает «как создать X» один раз, а дальше отдаёт готовые объекты. В KMP это особенно ценно: граф общего модуля один, а платформенные реализации (драйвер БД, движок сети) подставляются на своих местах.
Описание модуля
// commonMain
val sharedModule = module {
single { HttpClient { install(ContentNegotiation) { json() } } }
single { OrderApi(get()) }
single<OrderRepository> { OrderRepositoryImpl(get(), get()) }
factory { OrderViewModel(get()) }
}single — один экземпляр на приложение, factory — новый при каждом запросе. get() просит контейнер подставить нужную зависимость.
Платформенные модули
Платформенные реализации описывают в платформенных модулях:
// androidMain
val androidModule = module {
single<SqlDriver> { AndroidSqliteDriver(Schema, get(), "app.db") }
}А запуск Koin происходит на каждой платформе со своим набором модулей: общий + платформенный.
Как работает под капотом
В отличие от Dagger/Hilt, которые генерируют код графа на компиляции, Koin строит граф в рантайме: модуль — это словарь «тип → как создать». Когда запрашивают OrderRepository, Koin находит его описание, рекурсивно создаёт зависимости через get() и возвращает объект. Плата — отсутствие проверки графа на этапе компиляции (ошибку «нет такой зависимости» увидите в рантайме). Зато Koin не требует кодогенерации, что упрощает его работу на Kotlin/Native, где не все генераторы доступны. Поэтому в KMP Koin — популярный выбор.
Частые ошибки
Забыть зарегистрировать платформенный модуль при старте — и получить рантайм-ошибку об отсутствии SqlDriver. Вторая — делать single там, где нужен factory (например, для ViewModel, привязанной к экрану), и держать устаревшее состояние. Третья — тащить в Koin-модуль платформенные классы внутри commonMain; платформенное регистрируется в платформенных модулях.
Итоги
- Koin описывает граф зависимостей модулями на Kotlin DSL.
- Граф строится в рантайме, без кодогенерации — удобно для Native.
- Платформенные реализации регистрируются в платформенных модулях.
singleдля общих сервисов,factory— для одноразовых объектов.