В Android-проектах Koin остаётся одной из самых популярных DI-библиотек, особенно в MVP/MVI и Compose-приложениях. Она проста в настройке, гибкая и хорошо работает в больших кодовых базах.

Но многие команды, стремясь к модульности и тестопригодности, начинают активно использовать функции loadKoinModules() и unloadKoinModules(). И здесь разработчики часто натыкаются на странную ошибку:

org.koin.core.error.NoBeanDefFoundException: No definition found for …

В этой статье я разберу:

  • что происходит под капотом при loadKoinModules/unloadKoinModules;

  • почему возникает ошибка definition not found;

  • как правильно организовать модули Koin без динамической загрузки.

Проблема

Сценарий

Допустим, у нас проект со множеством feature-модулей. Вы хотите подгружать зависимости только тогда, когда модуль реально нужен, например, в on-demand фичах или UI-тестах. Вы делаете что-то вроде:

На первый взгляд – всё ок. Но при быстром переключении между экранами, в UI-тестах или при работе background-сервисов неожиданно вылезает NoBeanDefFoundException.

Почему так происходит

  1. Koin работает с глобальным graph’ом.

    loadKoinModules() добавляет определения в уже работающий контейнер. unloadKoinModules() их удаляет. Если параллельно что-то пытается получить зависимость — оно упадёт.

  2. Асинхронность Android.

    Даже если вы думаете, что «экран закрылся – зависимости больше не нужны», на деле корутины, WorkManager или callback-и могут продолжать жить и запрашивать зависимости.

  3. ViewModelStore и жизненный цикл.

    ViewModel может пережить activity или fragment (особенно при смене конфигурации). Если в этот момент вы выгрузите модуль, то при следующем get() он просто не найдёт определение.

Пример реальной проблемы

Если Activity пересоздаётся при повороте экрана или пользователь быстро переключается, ViewModel может ещё жить, а модуль уже выгружен → крэш.

Как стоит делать

1. Инициализируйте модули один раз

Загрузите все модули при старте приложения или используйте DI-компоненты в стиле Hilt/Dagger (для тех же целей). Не выгружайте их динамически, если нет крайней необходимости.

2. Если нужна условная регистрация – используйте флаги, а не выгрузку

Вместо удаления модуля используйте stub-имплементации или условные фабрики:

3. Для UI-тестов — отдельный KoinApp

Вместо loadKoinModules/unloadKoinModules проще запустить Koin заново с тестовыми модулями:

Рекомендация

loadKoinModules и особенно unloadKoinModules — это «escape hatch», а не штатный инструмент. Они могут быть полезны для плагинов или SDK, но не для обычных экранов и фичей. В большинстве production-сценариев надёжнее:

  • загружать модули один раз при старте приложения,

  • использовать условные реализации,

  • или перезапускать Koin целиком в тестах.

Заключение

Koin — отличный инструмент, но важно помнить, что его контейнер глобальный. Динамическая загрузка и выгрузка модулей приводит к состояниям гонки и definition not found ошибкам.

Поэтому подход — минимизировать динамическое управление модулями и проектировать зависимости так, чтобы их можно было безопасно инжектить в любом месте приложения.

Комментарии (0)