Как рефлексия в Kotlin помогает автоматизировать работу с Koin
Работая над большим многомодульным проектом, я нередко попадаю в ситуацию, когда забываю добавить новый модуль в 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
— интерфейс из :data
модуля моего проекта.
SomeModule
— класс с реализацией зависимостей для Koin.
Далее вместо обычной декларации модулей >>
startKoin {
modules(someModule)
}
>> «Пройдемся по всем наследникам и вытащим из них модули»
import kotlin.reflect.KClass
import org.koin.core.module.Module
fun KClass.collectModules(): List =
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!