Введение
«У вас в проекте Koin? А почему не Hilt?» — вопрос, после которого половина кандидатов начинает мямлить. И зря: Koin — не «игрушечный DI для маленьких проектов», а полноценный фреймворк со своими сильными сторонами. Разберём, как он устроен, и научимся защищать этот выбор на собеседовании.
Koin — это DI-контейнер для Kotlin, который работает без кодогенерации: зависимости описываются декларативно на самом Kotlin, а граф собирается в рантайме. Никаких аннотаций-обработчиков, никакого KSP — просто подключил библиотеку и написал модуль.
Главное отличие от Hilt и Dagger: в Koin нет этапа генерации кода. Это делает его проще в настройке и быстрее в подключении, но ошибки в графе проявляются при запуске приложения, а не при компиляции. Дальше разберём, как это работает на практике.
Что такое Koin: DI без кодогенерации
С точки зрения архитектуры Koin — это Service Locator с DSL поверх него: контейнер хранит определения объектов и по запросу возвращает экземпляры, самостоятельно разрешая их зависимости. В этом его принципиальное отличие от Dagger и Hilt, которые генерируют код Dagger-компонентов при компиляции.
Чем это оборачивается на практике:
- Простота подключения. Одна зависимость в Gradle, никаких плагинов и процессоров аннотаций.
- Читаемые модули. Описание графа — это обычный Kotlin-код: его легко читать, рефакторить и переиспользовать между модулями.
- Быстрый старт. Не нужно изучать компоненты, скоупы и иерархии — три ключевых слова DSL закрывают 90% задач.
- Цена — безопасность типов. Неверная привязка выстрелит в рантайме (часто при первом обращении к зависимости), а не на сборке.
Как отвечать на вопрос «зачем Koin»
Сильный ответ на собеседовании: Koin выбирают, когда ценят простоту и скорость подключения, а цена — отсутствие проверки графа при компиляции и медленнее разрешение зависимостей в рантайме. Если в проекте сотни классов и сложный граф — это аргумент за Hilt, а не за Koin.
Модули и DSL: single и factory
Вся конфигурация Koin живёт в модулях. Модуль — это просто функция module { ... }, внутри которой объявляются зависимости:
val appModule = module {
single<NotesRepository> { NotesRepositoryImpl() }
single<ApiService> { RetrofitProvider.create() }
factory { NotesPresenter(get()) }
}Два ключевых слова, которые обязательно спрашивают на собеседовании:
single— создаёт один экземпляр на всё приложение. Подходит для репозиториев, API-клиентов, баз данных — всего, что должно жить в единственном числе.factory— создаёт новый экземпляр при каждом запросе. Подходит для объектов с коротким временем жизни: например, презентеров, которые не должны держаться в памяти между экранами.
Внутри конструкций виден ещё один важный механизм — get(): так Koin понимает, что зависимость нужно взять из контейнера. NotesPresenter(get()) означает «создай NotesPresenter, передав ему найденный в графе экземпляр NotesRepository».
Если класс можно создать через конструктор автоматически, используется сокращённая форма с bind для привязки интерфейса к реализации:
val appModule = module {
single<NotesRepositoryImpl>() bind NotesRepository::class
}Скоупы тоже есть: scope { ... } позволяет ограничить время жизни группы объектов, например, в рамках сессии пользователя. Но в большинстве проектов достаточно single и factory.
Типичная ошибка новичка — делать всё single, потому что «так меньше кода». В итоге фабрики, которые должны создаваться на каждый экран, превращаются в долгоживущие синглтоны, и в приложении появляются утечки и общий мутируемый стейт. Вопрос «почему здесь single, а не factory» — один из моих любимых на собеседованиях.
Koin в Android: старт, инжекция, ViewModel
Для Android Koin подключается одной зависимостью — io.insert-koin:koin-android. Запуск происходит в классе Application:
class App : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidLogger()
androidContext(this@App)
modules(appModule)
}
}
}androidLogger()— логирование работы контейнера;androidContext()— передаёт контекст приложения, чтобы Koin мог отдавать его как зависимость;modules()— список модулей, из которых собирается граф.
Инжекция в Android-компоненты — через делегат by inject():
class MainActivity : AppCompatActivity() {
private val notesRepository: NotesRepository by inject()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// notesRepository уже готов к использованию
}
}А для ViewModel в модуле объявляется специальное слово viewModel, а в Activity — делегат by viewModel():
class NotesViewModel(
private val repository: NotesRepository
) : ViewModel() {
val notes: StateFlow<List<Note>> = repository.notes
}val appModule = module {
single<NotesRepository> { NotesRepositoryImpl() }
viewModel { NotesViewModel(get()) }
}class MainActivity : AppCompatActivity() {
private val viewModel: NotesViewModel by viewModel()
// ...
}by viewModel() создаёт нужную ViewModelProvider.Factory автоматически и привязывает ViewModel к жизненному циклу — вручную фабрику писать не нужно. Это, кстати, ещё один плюс Koin перед ручным DI: ViewModelFactory — одно из самых скучных мест в Android-коде.
- Подключение: зависимость
koin-android, запуск черезstartKoinв Application. - Модули:
module { ... }— место объявления зависимостей;single— синглтон,factory— новый экземпляр на запрос. - Инжекция:
by inject()для обычных объектов,by viewModel()для ViewModel. - Связывание зависимостей:
get()внутри конструкций иbindдля привязки интерфейса к реализации. - Цена простоты: граф не проверяется при компиляции, ошибки приходят из рантайма.
Koin vs Hilt: что выбрать
На собеседовании вопрос «Koin или Hilt» — это проверка не знания библиотек, а умения взвешивать инженерные компромиссы. Хороший ответ начинается не с «я люблю Koin», а с контекста.
| Критерий | Koin | Hilt |
|---|---|---|
| Кодогенерация | Нет | Да (Dagger-код при компиляции) |
| Ошибки графа | В рантайме | При сборке |
| Настройка | Одна зависимость | Плагин + обработчик аннотаций |
| Порог входа | Низкий | Средний |
| Скорость разрешения | Медленнее | Быстрее |
| Интеграция с Android | Хорошая | Официальный стандарт Google |
Когда выбирать Koin:
- проект на Kotlin Multiplatform — Koin работает на всех платформах;
- небольшая кодовая база, где граф простой и «читается глазами»;
- команда хочет минимум магии и понятный каждому код;
- быстрое прототипирование без лишней настройки.
Когда выбирать Hilt:
- крупный проект со сложным графом — проверка при компиляции экономит часы отладки;
- активное использование скоупов и вложенных компонентов;
- стандартная экосистема Google, интеграции с WorkManager и Compose «из коробки».
Честный вывод
Ни Koin, ни Hilt не «лучше» в вакууме. На собеседовании засчитывается ответ: «Мы выбрали Koin, потому что проект небольшой и мы ценим читаемость DSL; если граф разрастётся, миграция на Hilt будет стоить пару недель — и это осознанный риск». Умение называть цену решения — признак middle-мышления.
Пара слов обо мне: я Рустем Бикбулатов, senior Android-разработчик и ментор Яндекс Практикума. Провожу технические собеседования, за плечами более 100 разборов кандидатов. Помогаю разработчикам с опытом 1–2 года закрыть пробелы в Kotlin, Coroutines и архитектуре и выйти на middle за 4–8 недель.
Нужна помощь в переходе на middle?
Бесплатная диагностика: разберу ваши пробелы в DI и архитектуре и составлю план роста за 30–40 минут. Без обязательств — если менторство вам не подойдёт, честно скажу.
Итоги
Koin — простой и прагматичный DI-контейнер, который отлично закрывает задачу в небольших и средних проектах.
- Суть: DSL-модули на Kotlin, граф собирается в рантайме, без кодогенерации и аннотаций.
- Ключевые слова:
single— синглтон,factory— новый экземпляр,viewModel— для ViewModel,by inject()иby viewModel()— для инжекции. - Компромисс: простота и скорость подключения в обмен на проверку графа только в рантайме.
Главное, что нужно запомнить
На собеседовании не спрашивают «что такое Koin» — спрашивают «почему Koin, а не Hilt». Готовый ответ: Koin — это DI без кодогенерации, где зависимости описываются Kotlin-модулями и резолвятся в рантайме; он проще, но Hilt даёт проверку графа при компиляции и официальный статус стандарта.
Если не уверены, что сможете так же уверенно ответить про остальные темы собеседования — архитектуру, корутины, жизненный цикл — не нужно зубрить всё подряд. Напишите в Telegram, разберём вашу ситуацию и составим план, что закрывать в первую очередь.