Koin Annotations, Part 1

m0skit0
3 min readFeb 9, 2020

--

wakka wakka wakka wakka

Are you using Koin for dependency injection? Don’t you think it’s pretty inconvenient to manually add a Koin module to the list of loaded modules every time you create one? Have you ever forgotten to add one and then got the lovely NoBeanDefFoundException at runtime? Let’s see how to make this less error-prone by using compile-time annotations ;)

You can find repository for this project here.

Manually loading Koin modules

If, like me, you’re a user of Koin, you know that to load Koin modules you need to either use startKoin or loadKoinModules.

So every time you declare a new Koin module, you have to go back to this list and add it.

This is inefficient and error-prone as we all Koin users already know.

Compile-time annotations

Compile-time annotations can help us auto-generate the code for this and having to write the module loading once instead of requiring a manual update each time we create a new Koin module.

The idea is to return Koin modules from functions and annotate each of these functions with our custom annotation, then process these annotations to generate the code that generates the list of modules to be loaded. Something like the following:

Then we can just annotate our Koin module functions with @KoinModule to have them automatically returned by getAllKoinModules() function, which we will generate on-the-fly during compile time. But how can we do this?

KoinModule annotation

The first is define our annotation. This is pretty straightforward:

Target are functions and retention policy is source (annotations will be stripped in compiled code, we don’t need them).

Define our annotation processor

To ease annotation processing we will use Google’s Auto Service libraries

Then we define our annotation processor

@AutoService will automatically register our processor with compile-time processors. We also define which annotations this annotation processor will process using @SupportedAnnotationTypes (with our previously defined KoinModule annotation).

Process annotations

Now our annotation processor’s process method will be invoked.

First thing to do in the method is validate the elements we’re receiving. If the set of elements is empty, we will return false.

Next we need to validate elements annotated with @KoinModule. For this we first need to extract them from the RoundEnvironment.

For the checks we’re going to define a private method to print information and abort compilation when we find an error.

First check we need to perform is to validate that all the annotated elements are methods.

Then we need to check the method adheres to a specific set of conditions, namely:

  • No parameters.
  • Return type is Koin’s Module.
  • Public visibility.

If all these checks are ok, we store the necessary information from these functions to be able to call them later, namely the function name and its enclosing class. For this we define the following data class:

Then a mapping function that maps an Element to a KoinModuleFunction.

Resuming, now we have the following processor class

We’re ready to generate our code, which we will do in part 2 ;)

--

--