Koin Compiler – Sandbox

The goal of Koin compiler & Annotations project is to help declare Koin definition in a very fast and intuitive way, and generate all underlying Koin DSL.

Koin Annotation Processing
🚀

Koin code generation is mostly instant for Koin: just a few lines to generate. It is really easily readable/debuggable.

Even with ~~1000 of definitions, project compilation has almost no impact.

Here with such power from Ksp project, we can bring ease even more Kotlin dependency injection. We keep Koin API as it. We just avoid you to write definitions and modules.

Automatic definition binding

When tagging a component to be defined in Koin, we can easily declare all related supertypes directly:

@Single
class ElectricHeater : Heater 

would generate definition:

single { ElectricHeater() } bind Heater::class

Using generated content

The only thing to setup in particular is the org.koin.ksp.generated.* import as follow, to be able to use genrated extensions:

import org.koin.ksp.generated.*

fun main() {
    startKoin {
        printLogger()
        // ... generated features
    }
    // ...
}

Annotated definitions without modules

We need to use a default module with defaultModule() extension:

import org.koin.ksp.generated.*

fun main() {
    startKoin {
        printLogger()
        // generated default module
        defaultModule()
    }
    // ...
}

tag with @Single any components:

@Single
class CoffeeMaker(private val pump: Pump, private val heater: Heater)

@Single
class Thermosiphon(private val heater: Heater) : Pump

@Single
class ElectricHeater : Heater 

What is generated:

package org.koin.ksp.generated
// imports ...

fun KoinApplication.defaultModule() = modules(defaultModule)
val defaultModule = module {
    // definitions here ...
}

Class Module and functions as definitions

We want to use a CoffeeAppModule module class. The .module extension on CoffeeAppModule class will be generated:

import org.koin.ksp.generated.*

fun main() {
    startKoin {
        printLogger()
        modules(
        // generated .module extension on module class
          CoffeeAppModule().module
        )
    }
    // ...
}

Let’s define a class module with annotations on functions:

@Module
class CoffeeAppModule {

  @Single
  fun heater() : Heater = ElectricHeater()

  @Single
  fun pump(heater: Heater) : Pump = Thermosiphon(heater)

  @Single
  fun coffeeMaker(heater: Heater, pump: Pump) = CoffeeMaker(pump, heater)
}

What is generated:

package org.koin.ksp.generated
// imports

val CoffeeAppModuleModule = module {
  // generated module instance
  val moduleInstance = org.koin.example.coffee.CoffeeAppModule()
  // definitions using functions here ...
}
val org.koin.example.coffee.CoffeeAppModule.module : org.koin.core.module.Module get() = CoffeeAppModuleModule

Scan all definitions into a Class Module

Rather than defining each component, we can allow a module to scan definitions for current package and sub packages:

import org.koin.ksp.generated.*

fun main() {
    startKoin {
        printLogger()
        modules(
        // generated .module extension on moduel class
          CoffeeAppModule().module
        )
    }
    // ...
}

Let’s define a class module, use @ComponentScan to scan definitions for a given module, @Single on components:

@Module
@ComponentScan
class CoffeeAppModule

What is generated:

package org.koin.ksp.generated
// imports

val CoffeeAppModuleModule = module {
  val moduleInstance = org.koin.example.coffee.CoffeeAppModule()
  // definitions here ...
}
val org.koin.example.coffee.CoffeeAppModule.module : org.koin.core.module.Module get() = CoffeeAppModuleModule

We can also specify what package to scan in @ComponentScan value. Below we scan annotated components in org.koin.example.test package:

@Module
@ComponentScan("org.koin.example.test")
class CoffeeAppModule

Unmatched definitions fallback in default module

In case of using @ComponentScan, if any definition is tagged but not associated to a declared module, this definition will fallback into the defaultModule

import org.koin.ksp.generated.*

fun main() {
    startKoin {
        printLogger()
        modules(
          // generated default module
          defaultModule,
        // generated .module extension on module class
          CoffeeAppModule().module
        )
    }
    // ...
}

Class Module, mixing declarations

As with previous case:

import org.koin.ksp.generated.*

fun main() {
    startKoin {
        printLogger()
        modules(
        // generated .module extension on moduel class
          CoffeeAppModule().module
        )
    }
    // ...
}

We can combine @ComponentScan & annotated functions definition:

@Module
@ComponentScan
class CoffeeAppModule {

  @Single
  fun coffeeMaker(heater: Heater, pump: Pump) = CoffeeMaker(pump, heater)
}

We keep @Single annotations on needed components:

@Single
class CoffeeMaker(private val pump: Pump, private val heater: Heater)

@Single
class Thermosiphon(private val heater: Heater) : Pump

class ElectricHeater : Heater 

Generated content will add all definitions for module generation, like previous case.

Multiple Class Modules

Any class module tagged with @Module will be generated. Just import the module like follow:

@Module
@ComponentScan
class OtherModule

Just use it with the .module generated extension:

import org.koin.ksp.generated.*

startKoin {
  printLogger()
  modules(
    CoffeeAppModule().module,
    // new module here, with .module generated extension
    OtherModule().module
  )
}

TODO
🚧

Basic Definition Options: (In Progress)

  • Create at start

  • Qualifier (@Qualifier) => Type & Function

  • Generic for other keywords with factory (help for later Android)

  • Android Keywords

    • @ViewModel
    • @Fragment
    • @Worker

Parameter Injection (@Param)

  • Ctor
  • Fun

Property (@Property)

  • getProperty(key)

Scope Structure (@Scope)

  • @Scope on a type
  • @ScopedIn?
  • @ScopedIn for factory definitions? (visibility problem)

GitHub

https://github.com/InsertKoinIO/koin-compiler-playground