How reflection in Kotlin helps automate working with Koin

When working on a large multi-module project, I often find myself in a situation where I forget to add a new module to startKoinwhy I catch org.koin.core.error.NoDefinitionFoundException – lack of declaration of the type that Koin is trying to inject, and therefore, since, in my opinion, the main concept of IT is the automation of our lives, it would be nice to automate this aspect as well.

if (Koin != Hilt) return

At first I tried to wrap a module so that when called, this module would be added to a certain list, which can then be used for declaration, but with more detailed delving into Koin, it turned out that it makes injections at runtime, and not at the compilation stage, like Hilt, and therefore, this option was unsuccessful.

sealed, sealed, sealed

I remembered the wonderful sealed and again, by the way, sealed classes in which all descendants are known at the compilation stage, which means something can be done about it.

Reflection

Reflection in programming is the ability of a program to analyze its structure and behavior during execution. In Kotlin, a programming language compatible with Java, reflection is a powerful tool that allows programmers to obtain information about classes, functions, variables, and methods during program execution.
However, you should be careful when using reflection. It can reduce performance and lead to runtime errors because it is not guaranteed that all operations will be safe and correct.
Source: https://kolesnikovdev.ru/refleksiya-v-kotlin-prostye-primery-i-ispol

Let's go back to the previous part about sealed: The most common use I've seen in code is branching without defaulting – if without elsewhich allows you to work with the instance knowing that all possible options are taken into account, for example, logging network errors.

In context sealed class reflection allows you to find out the list of heirs – this is the key point of this article.

Solving the problem

My idea is not a panacea, but only one of the options, and perhaps in the comments someone will suggest a more effective and safe way.

Instead of the usual module declaration >>

val someModule = module {}

>> I made a basic module interface, a sealed interface and its implementation like this:

import org.koin.core.module.Module

interface KoinModule {
    val module: Module
}

sealed interface DataModule : KoinModule

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

KoinModule – interface for convenient implementation of methods

DataModule – interface from :data module of my project.

SomeModule – a class with implementation of dependencies for Koin.

Next, instead of the usual module declaration >>

startKoin {
    modules(someModule)
}

>> “Let's go through all the heirs and pull out the modules from them”

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()

With reflection using sealedSubclasses get a list of all heirs DataModule::classtaking from each the one we need module: among the constructors, let’s take the first one (it’s also the only one), call it, and map by the required field. I use it in generics KoinModuleto set a single method for all project modules.

And, in fact, the declaration:

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

Was:

Became:

In my opinion, all this is cool, but as one very good person named N. says: “The main thing is not to forget step 3 of sudo)”

No errors, no warnings, gentlemen and ladies!

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *