Работая над большим многомодульным проектом, я нередко попадаю в ситуацию, когда забываю добавить новый модуль в startKoin, из-за чего ловлю org.koin.core.error.NoDefinitionFoundException - отсутствие объявления типа, инъекцию которого пытается сделать Koin, и поэтому, так как, на мой взгляд, главная концепция IT - автоматизация нашей жизни, неплохо было бы автоматизировать и этот аспект.

if (Koin != Hilt) return

Сначала я попытался сделать обертку модулем так, чтобы при вызове этот модуль добавлялся в некий список, который потом можно использовать для декларации, но с более детальным углублением в Koin, оказалось, он делает инъекции в рантайме, а не на этапе компиляции, как Hilt, и поэтому, такой вариант был безуспешным.

sealed, sealed, sealed

Я вспомнил про чудесные sealed и еще раз, кстати, sealed классы, в которых все наследники известны на этапе компиляции, а значит, с этим можно что-то сделать.

Рефлексия

Reflection (рефлексия) в программировании — это возможность программы анализировать свою структуру и поведение во время выполнения. В Kotlin, языке программирования, совместимом с Java, рефлексия является мощным инструментом, позволяющим программистам получать информацию о классах, функциях, переменных и методах во время выполнения программы.
Однако, следует быть осторожным, используя рефлексию. Она может снизить производительность и привести к ошибкам времени выполнения, так как не гарантируется, что все операции будут безопасными и корректными.
Источник: https://kolesnikovdev.ru/refleksiya-v-kotlin-prostye-primery-i-ispol

Вернемся к предыдущей части про sealed: самое частое использование, которое я видел в коде - ветвление без дефолта - if без else, что позволяет работать с экземпляром, зная, что все возможные варианты учтены, например, логирование сетевых ошибок.

В контексте sealed классов рефлексия позволяет узнать список наследников - это ключевой момент данной статьи.

Решение проблемы

Моя идея - не панацея, а лишь один из вариантов, и, возможно, в комментариях кто-нибудь предложит более эффективный и безопасный способ.

Вместо обычного объявления модуля >>

val someModule = module {}

>> Я сделал базовый интерфейс модуля, sealed интерфейс и его реализацию следующим образом:

import org.koin.core.module.Module

interface KoinModule {
    val module: Module
}

sealed interface DataModule : KoinModule

class SomeModule : DataModule {
    override val module: Module = module {}
}

KoinModule - интерфейс для удобной реализации методов

DataModule - sealed интерфейс из :data модуля моего проекта.

SomeModule - класс с реализацией зависимостей для Koin.

Далее вместо обычной декларации модулей >>

startKoin {
    modules(someModule)
}

>> "Пройдемся по всем наследникам и вытащим из них модули"

import kotlin.reflect.KClass
import org.koin.core.module.Module

fun <T : KoinModule> KClass<T>.collectModules(): List<Module> =
    sealedSubclasses.map {
        it.constructors.first().call().module
    }
    
val dataModules = DataModule::class.collectModules()

С рефлексией с помощью sealedSubclasses получим список всех наследников DataModule::class, взяв из каждого нужный нам module: среди конструкторов возьмем первый (он же и единственный), вызовем его, и map по нужному полю. В дженерике использую KoinModule, чтобы задать единый метод для всех модулей проекта.

И, собственно, декларация:

startKoin {
    androidContext(this@App)
    modules(dataModules)
}

Было:

Стало:

На мой взгляд, всё это классно, но как говорит один очень хороший человек по имени Н.: "Главное не забывать 3 пункт sudo)"

No errors, no warnings, gentlemen and ladies!

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


  1. Guitariz
    04.11.2024 06:15

    Разработчики, уважаемые - не используйте рефлексию примерно никогда. У вас есть все инструменты, чтобы писать красивый, понятный, плоский и структурированный код.


  1. kirich1409
    04.11.2024 06:15

    С задачей сбора всех модулей уже может cправиться Koin Annotations. Работает через кодогенерацию и никакой доп. рефлексии