The Koin DSL

Work In Progress

This section covers the Koin DSL

Koin is a DSL first framework: you must declare things with the DSL to make it works. The idea behind is to avoid the use of annotations or other any proxy/code generation mechanisms. We want to stay simple and make Koin less intrusive as possible.

The Koin DSL summary

A quick recap of the Koin DSL keywords:

  • applicationContext - create a Koin Module
  • provide - provide a bean definition (can use bind)
  • factory - provide a factory bean definition (alias to provide(isSingleton = false)) (can’t use bind)
  • bean - provide a bean definition (alias to provide)(can’t use bind)
  • bind - additional Kotlin type binding for given bean definition
  • get - resolve a component dependency
  • getProperty - resolve a property
  • context - declare a logical context

Declare a Koin module

To declare a Koin module, we need to use the applicationContext function. This function returns a Koin Module and is the start of every component definition in Koin. The type returned by this function can be omited, thanks to Kotlin type inference. We can write a module like:

val myModule = applicationContext {
    // your definitions here
}

We encourage to use val to define Koin modules, as a module should be defined once. You are ready to provide definitions.

Once written, a module (or modules) will be started with the neede startKoin() function, depending on your chosen runtimes (starting Koin )

Provide definitions

The core idea of Koin is to provide component definitions (aka bean definitions), in order to assemble them and inject them by constructor. Those definitions will be embedded in modules.

To provide a new definition, we have the provide keyword. It has parameters:

  • name (optional, default value is “”) - name for that bean definition (used for name resolution)
  • isSingleton (optional, default value is true) - define if the bean definition will be singleton or factory
  • definition - a lambda function, describing how to build the component - This function generally call the constructor of the class component

Given class MyComponent()

We can declare it:

val myModule = applicationContext {
    provide { MyComponent() } //just call the constructor of MyComponent
}

Named dependencies

You can provide a name to a provided component:

val myModule = applicationContext {
    provide("MyCmp") { MyComponent() } //will be resolved with "MyCmp" name
}

This name is used when making/resolving dependency injection with a name. It is mostly used when you have to provide several definitions with the same type. You will be able to resolve a particular definition against its name.

Singleton or factory?

By default, definitions are made as singleton: Koin keeps the one reference for your component, until its drop. The other option is to make it factory: Koin provide a new instance each time you ask for the given component definition. Koin doesn’t keep any factory reference.

To have a unique MyComponent instance, we write it:

val myModule = applicationContext {
    provide { MyComponent() }
}

To generate a new instance each time we ask for MyComponent, we write it:

val myModule = applicationContext {
    provide(isSingleton = false) { MyComponent() }
}

Binding additional types

We usually provide more complex definitions, than just the implementation type. We regularly go with interfaces or abstract types.

Given a class and its interface:

interface MyService

class MyServiceImpl : MyService

We can declare it in several ways:

  • give a definition of MyServiceImpl and bind with additional type MyService
val myModule = applicationContext {
    // Define bean with type MyServiceImpl and additional type MyService
    provide { MyServiceImpl() } bind MyService::class
}
  • give a definition of MyService (if we not need to resolve MyServiceImpl)
val myModule = applicationContext {
    // Define bean with type MyService
    provide { MyServiceImpl() as MyService }
}

In fine, we will use the same MyServiceImpl class to make our definition, but we will resolve it a bit diffrently against their types.

Aliases (bean, factory …)

The provide keyword describes a component with the full options. But Koin gives somes aliases to write it faster, but with less parameters:

  • factory is equivalent to provide(isSingleton = false), but you can’t use bind or additional options on it
  • bean is equivalent to provide, but you can’t use bind or additional options on it (What’s the interest, just a shorter name to write beans without additional bindings)

Then, we can write:

val myModule = applicationContext {
    // Define factory bean with type MyService
    factory { MyServiceImpl() as MyService }
}
val myModule = applicationContext {
    // Define bean with type MyService
    bean { MyServiceImpl() as MyService }
}

If you need additional typing or other options, you will have to go with provide.

Dependency resolution

Once we have some definitions, we want to assemble them together. That’s the purpose of the get() function. Use it in your component constructor, and when the bean definition will be called, Koin will resolve the needed component dependency. We push to use constructor dependency injection pattern in the way that, all definitions are calling components constructors.

Given some classes:

class MyComponentA
class myComponentB(val a : MyComponentA)

Resolution by type

Here we can describe our components like:

val myModule = applicationContext {
    bean { MyComponentA() }
    bean { MyComponentB(get<MyComponentA>()) }
}

When calling definition for type MyComponentB, Koin will follow the get<MyComponentA>() function to resolve the needed dependency. With Kotlin, we don’t need to specify type as Kotlin is making type inference. we can just write get() as follow:

val myModule = applicationContext {
    bean { MyComponentA() }
    bean { MyComponentB(get()) }
}

Resolution by name

The above example use dependency reoslution by type. If we want to provide several definitions of the same type, we have to resolve dependency by name:

val myModule = applicationContext {
    bean("default") { MyComponentA() }
    bean("other") { MyComponentA() }
    bean { MyComponentB(get("default")) }
}

Multiple Definitions

Koin allows you to provide several definitions of the same type. But you won’t be able to resolve it directly by type:

interface MyService

class MyServiceImpl : MyService

class OtherServiceImpl : MyService

Definition override

val myModule = applicationContext {
    // Two definitions for MyService type
    bean { MyServiceImpl() as MyService }
    bean { OtherServiceImpl() as MyService }
}

Koin can’t choose between the MyServiceImpl or OtherServiceImpl, and last definition will override the existing one. a definition is overriden when two definitions have the same name and type.

Multiple definitions with different names

You can provide a name to your bean definitions, and later resolve it by name:

val myModule = applicationContext {
    // Two definitions for MyService type
    bean("default") { MyServiceImpl() as MyService }
    bean("other") { OtherServiceImpl() as MyService }
}

Lazy linking definitions / Imports

All your bean definitions doesn’t have to be in the same module. You can lazy link (or imports) definitions from others modules:

Given classes:

class MyComponentA
class myComponentB(val a : MyComponentA)

Here we can have several modules:

val myModuleA = applicationContext {
    bean { MyComponentA() }
}

val myModuleB = applicationContext {
    bean { MyComponentB(get<MyComponentA>()) }
}

myModuleA can be started alone, whereas you won’t be able to start MyComponentB from myModuleB without a module that provides a defintion of MyComponentA.

Koin starter function asks for module list to start :

  • to resolve MyComponentA only, you will start with startKoin(listOf(myModuleA))
  • to resolve MyComponentA and MyComponentB, you will start with startKoin(listOf(myModuleA,myModuleB))

Properties

You can inject properties directly into your modules, with getProperty() (Check the properties section to see how to provide properties in Koin).

Given class MyService(val url : String)

We can inject a property named ‘URL’:

val myModule = applicationContext {
    // will resolve property name 'URL'
    bean { MyService(getProperty("URL")) }
}

If needed, we can use a default value of this property (if property is set later):

val myModule = applicationContext {
    // will resolve property name 'URL' if available, else will give "http://default_url"
    bean { MyService(getProperty("URL","http://default_url")) }
}

DSL extensions

Android Architecture

The koin-android-architecture brings the following DSL extensions:

  • viewModel {} - declare a factory component, and bind it as a ViewModel component to be injected with getViewModel() or by viewModel()

Spark

The koin-spark brings the following DSL extensions:

  • controller {} - declare a bean component, and bind it as a SparkController component to be started with runControllers()

And now what?

You have defined your components thanks to Koin DSL. Dependency injection is made with constructor injection. You are now ready to use it in different platform and runtimes.

  • Start Koin for your platform (Android, Spark, ktor …)
  • Use/Inject it in your runtime components

Koin provides a starter function, depending on your chosen module. It will be in the form of: startKoin( <list of Module> )

Check the start Koin section to go further with starting.