This document covers the all koin projects.

Koin core: DSL, Container & API

Arnaud Giuliani <arnaud.g@insert-koin.io> :revnumber: 1.0.0-beta-6

Cover the core project features and core sub projects.

1. Introduction

This is the koin-core project manual. This project brings the core feature ok Koin, ready to be embedded in any Kotlin Runtime/SDK.

1.1. What is Koin?

Koin is pragmatic lightweight dependency injection framework for Kotlin developers. Written in pure Kotlin, using functional resolution only: no proxy, no code generation, no reflection. Koin is a DSL, a lightweight container and a pragmatic API.

1.2. Gradle setup

Add the koin-core dependency to your Gradle project:

// Add Jcenter to your repositories if needed
repositories {
    jcenter()
}
dependencies {
    // Koin for Android
    compile 'org.koin:koin-core:1.0.0-beta-6'
}

2. Koin DSL

Koin provides a DSL to help your describe your components with definitions and start the Koin container with those definitions and to instantiate them when needed.

2.1. Why a DSL?

Thanks to the power of Kotlin language, Koin provides a DSL to help your describe your app instead of annotate it or generate code for it. Those annotations need introspection analyze, proxying/code generation. Whereas with Kotlin DSL, we can use smart functional API to achieve the same goal: make dependency injection.

2.2. The Koin DSL

Below are the Koin DSL keywords:

  • module - create a Koin Module

  • factory - provide a factory bean definition

  • single - provide a singleton bean definition (also aliased as bean)

  • bind - add type to bind for given bean definition

  • get - resolve a component dependency

2.3. Writing a module

A Koin module is the space to declare all your components. Use the module function to declare a Koin module:

val myModule = module {
   // your dependencies here
}

In this module, you can declare components with single or factory functions, or declare inner modules with module function.

2.4. Defining a singleton

Declaring a singleton component means that Koin container will keep a unique instance of your declared component. Use the single function in a module to declare a singleton:

class MyService()

val myModule = module {

    // declare single instance for MyService class
    single { MyService() }
}

single & factory keywords help you declare your components through a lambda expression. this lambda describe the way that you build your component. Usually we instantiate components via their constructors, but you can also use any expression.

factory or single { Class constructor / expression }

The result type of your lambda is the main type of your component

2.5. Defining a factory

A factory component declaration is a definition that will gives you a new instance each time you ask for this definition (this instance is not retrained by Koin container, as it won’t inject this instance in other definitions later). Use the factory function with a lambda expression to build a component.

class Controller()

val myModule = module {

    // declare factory instance for Controller class
    factory { Controller() }
}

Koin containter doesn’t retain factory instances as it will give a new instance each time the definition is asked.

2.6. Resolving & injecting dependencies

Now that we can declare components definitions, we want to link instances with dependency injection. To resolve an instance in a Koin module, just use the get() function to the requested needed component instance. This get() function is usually used into constructor, to inject constructor values.

To make dependency injection with Koin container, we have to write it in constructor injection style: resolve depdendencies in class constructors. This way, your instance will be created with injected intances from Koin.

Let’s take an example with several classes:

// Presenter <- Service
class Service()
class Controller(val view : View)

val myModule = module {

    // declare Service as single intance
    single { Service() }
    // declare Controller as single instance, resolving View instance with get()
    single { Controller(get()) }
}

2.7. Binding an interface

A single or a factory definition use the type from the their given lambda definition: i.e single { T } The matched type of the definition is the only matched type from this expression.

Let’s take an example with a class and implemented interface:

// Service interface
interface Service{

    fun doSomething()
}

// Service Implementation
class ServiceImp() : Service{

    fun doSomething() { ... }
}

In a Koin module we can use the as cast Kotlin operator as follow:

val myModule = module {

    // Will match type ServiceImp only
    single { ServiceImp() }

    // Will match type Service only
    single { ServiceImp() as Service }

}

You can also use the inferred type form, to tell the requested type:

val myModule = module {

    // Will match type ServiceImp only
    single { ServiceImp() }

    // Will match type Service only
    single<Service> { ServiceImp() }

}

2.8. Binding additional type

In some cases, we want to match several types from just one definition.

Let’s take an example with a class and interface:

// Service interface
interface Service{

    fun doSomething()
}

// Service Implementation
class ServiceImp() : Service{

    fun doSomething() { ... }
}

To make a definition bind additional types, we use the bind operator with a class:

val myModule = module {

    // Will match types ServiceImp & Service
    single { ServiceImp() } bind Service::class
}

2.9. Naming a definition

A definition has by default an empty name. But you can declare a name to your definition, to help you distinguish two definitions about the same type:

Just request your definition with its name:

val myModule = module {
    single("default") { ServiceImpl() as Service }
    single("test") { ServiceImpl() as Service }
}

val service : Service by inject(name = "default")

get() and by inject() functions let you specify a definition name if needed.

2.10. Declaring injection parameters

In any single or factory definition, you can use injection parameters: parameters that will be injected and used by your definition:

class Presenter(val view : View)

val myModule = module {
    single{ view : View -> Presenter(view) }
}

In contrary to resolved dependencies (resolved with with get()), injection parameters are parameters passed through the resolution API. This means that those parameters are values passed with get() and by inject(), with the parametersOf function:

val presenter : Presenter by inject { parametersOf(view) }

Further reading in the injection parameters section.

2.11. Conflicting definitions

Conflicting definitions occurs when you have:

  • several definitions that are matching the same type

  • defined a circular dependency

In the first case, you can name your definition to distinguish them. You can also use modules to alter resolution visibility. In last case, you can also specify a module path when you request a dependency from a KoinComponent.

val myModule = module("org.sample") {


    module("org.demo"){
        single{ ServiceImpl() as Service}
    }

    module("org.sample"){
        single{ ServiceImpl() as Service}
    }
}

// Request dependency from /org/sample namespace
val service : Service by inject(module = "org.sample")

For a circular dependency problem, you are in the situation where A←B & B←A. You have to review your relation between A & B, and break the circularity to resolve your situation.

2.12. Definition flags

Koin DSL also proposes some flags.

2.12.1. Create instances at start

A definition or a module can be flagged as createOnStart, to be created at start (or when you want). First set the createOnStart flag on your module or on your definition.

CreateOnStart flag at definition level
val myModuleA = module {

    single { ServiceImp() as Service }
}

val myModuleB = module {

    // eager creation for this definition
    single(createOnStart=true) { TestServiceImp() as Service }
}
CreateOnStart flag at module level
val myModuleA = module {

    single { ServiceImp() as Service }
}

val myModuleB = module(createOnStart=true) {

    single{ TestServiceImp() as Service }
}

The startKoin function will automatically create definition instances flagged with createOnStart.

// Start Koin modules
startKoin(listOf(myModuleA,myModuleB))

If you don’t want to create instances at start, jus use the createOnStart at false from the startKoin() function:

// Start Koin modules
startKoin(listOf(myModuleA,myModuleB), createOnStart = false)

if you need to load some definition at a special time (in a background thread instead of UI for example), just get/inject the desired components.

2.12.2. Overriding a definition or a module

Koin won’t allow you to redefinition an already existing definition (type,name,path …​). You will an an error if you try this:

Override not allowed
val myModuleA = module {

    single { ServiceImp() as Service }
}

val myModuleB = module {

    single { TestServiceImp() as Service }
}

// Will throw an BeanOverrideException
startKoin(listOf(myModuleA,myModuleB))

To allow definition overriding, you have to use the override parameter:

Override at definition level
val myModuleA = module {

    single { ServiceImp() as Service }
}

val myModuleB = module {

    // override for this definition
    single(override=true) { TestServiceImp() as Service }
}
Override at module level
val myModuleA = module {

    single { ServiceImp() as Service }
}

// Allow override for all definitions from module
val myModuleB = module(override=true) {

    single { TestServiceImp() as Service }
}

Order matters when listing modules and overriding definitions. You must have your overriding definitions in last of your module list.

3. Koin Modules

By using Koin, you describe definitions in modules. In this section we will see how to declare, organize & link your modules.

3.1. What is a Koin Module?

A Koin module is a space to gather Koin definition. It’s declared with the module function.

val myModule = module {
    // Your definitions ...
}

3.2. Module’s path

A module has a path, which represents a namespace to help you organize your definitions. The path is an optional parameter from the module { } function and its default value is root namespace.

Modules with paths:
// definitions in / (root) namespace
val aRootModule = module { ... }

// definitions in /org/sample namespace
val sampleModule = module("org.sample") { ... }

The default namespace separator is "."

If several modules are declaring the same namespace, their definitions will be in the same namespace. The Koin module’s path is also a good way to separate definitions in Koin.

You can also directly use a class to reuse its path as module name, with moduleName property:

Modules from class:
// definitions in /org/sample/UserSession
val sampleModule = module(UserSession::class.moduleName) { ... }

3.3. Inner modules

A module can also contains inner modules. An inner is a module declaration inside an existing module declaration. It’s also declared with module function, be an inner module must specify a path.

Module and an inner module:
// definitions in / (root) namespace
val aModule = module {

    // definitions in /org/sample namespace
    module("org.sample") {

    }
}

The example above is the equivalent of previous section’s example.

Modules path equivalent:
val sampleModule = module("org.sample") { ... }

// is equivalent to
val sampleModule = module {
    module("org") {
        module("sample") {
            // ...
        }
    }
}

3.4. Linking definitions between modules

Components doesn’t have to be necessarily in the same module. A module is a logical space to help you organize your definitions, and can depend on definitions from other module. Definitions are lazy, and then are resolved only when a a component is requesting it.

Let’s take an example, with linked components in separate modules:

// ComponentB <- ComponentA
class ComponentA()
class ComponentB(val componentA : ComponentA)

val moduleA = module {
    // Singleton ComponentA
    single { ComponentA() }
}

val moduleB = module {
    // Singleton ComponentB with linked instance ComponentA
    single { ComponentB(get()) }
}

Koin does’t have any import concept. Koin definitions are lazy: a Koin definition is started with Koin container but is not instanciated. An instance is created only a request for its type has been done.

We just have to declare list of used modules when we start our Koin container:

// Start Koin with moduleA & moduleB
startKoin(listOf(moduleA,moduleB))

Koin will then resolve dependencies from all given modules.

3.5. Assembling modules for implementation strategy

As definitions between modules are lazy, we can use modules to implement different strategy implementation: declare an implementation per module.

Let’s take an example, of a Repository and Datasource. A repository need a Datasource, and a Datasource can be implemented in 2 ways: Local or Remote.

class Repository(val datasource : Datasource)
interface Datasource
class LocalDatasource() : Datasource
class RemoteDatasource() : Datasource

We can declare those components in 3 modules: Repository and one per Datasource implementation:

val repositoryModule = module {
    single { Repository(get()) }
}

val localDatasourceModule = module {
    single { LocalDatasource() as Datasource }
}

val remoteDatasourceModule = module {
    single { RemoteDatasource() as Datasource }
}

Then we just need to launch Koin with the right combination of modules:

// Load Repository + Local Datasource definitions
startKoin(listOf(repositoryModule,localDatasourceModule))

// Load Repository + Remote Datasource definitions
startKoin(listOf(repositoryModule,remoteDatasourceModule))

3.6. Hierarchy & visibility

Visibility rule is quite simple: child modules can see their parents, but not the inverse. A definition from a child module, can see definitions in parents modules. Modules can’t share their definitions in divergent paths.

Let’s take an example:

// definitions in /
val rootModule = module {
    single { ComponentA() }
}
// definitions in /org
val orgModule = module("org") {
    single { ComponentB(...) }
}
// definitions in /org/sample
val sampleModule = module("org.sample") {
    single { ComponentC(...) }
}
// definitions in /org/demo
val demoModule = module("org.demo") {
    single { ComponentD(...) }
}

We have the following resolution possibility:

  • ComponentA can only see definitions from root (can only see `/, can’t see ComponentB, ComponentC & `ComponentD)

  • ComponentB can see definitions from org & root (can see / and /org - can resolve ComponentA - can’t see ComponentC & `ComponentD )

  • ComponentC can see definitions from sample, org & root (can see /, /org, /org/sample - can resolve ComponentA, ComponentB - can’t see ComponentD)

  • ComponentD can see definitions from demo, org & root (can see /, /org, /org/demo - can resolve ComponentA`, ComponentB - can’t see ComponentC)

3.7. Visibility & isolation

By declaring definitions in a module with a path, your component are then not visible from outside of this namespace. This is then very useful to protect visibility between modules.

When you are requesting a definition from a KoinComponent with get() or by inject(), you can specify the module in which you want to resolve. If you don’t specify it, Koin will resolve it from all modules. Else Koin will resolve it from the specified module namespace.

If we have modules as follow:

val myModule = module {

    module("org.demo"){
        single{ ServiceImpl() as Service}
    }

    module("org.sample"){
        single{ ServiceImpl() as Service}
    }
}

// Request dependency from /org/sample namespace
val service : Service by inject(module = "org.sample")

We can resolve definitions from this module with get() or get(module = "org.sample") (idem for by inject()).

3.8. Definition instances & modules

When the Koin container is instantiating a definition from a given module, this instance will be attached to the module’s path.

For a given module defined in /org/sample, all definitions are instantiated in its module path.

val myModule = module("org.sample") {
    // Service instance will be in /org/sample
    single{ Service() }
}

That also mean, that each definition is located in its namespace:

class Datasource()
class Repository(val datasource : Datasource)


val myModule = module {

    // Datasource instance will be in /
    single { Datasource() }

    module("org.sample") {
        // Repository instance will be in /org/sample
        single{ Repository(get()) }
    }
}

3.9. Releasing module instances

The other interest of a module, is the ability to drop instances for a given module path. This means that you can organize your components with modules and manage instances lifecycle. When you don’t need instances from a given module, you can drop them with the release function (KoinComponent).

val myModule = module {
    module("org.sample"){
        //...
    }
}

// drop instances from /org/sample module
release("org.sample")

Releasing a module will also release all its children.

Module release can also be done with class module and moduleName property:

val myModule = module {
    module(Usersession::class.moduleName){
        //...
    }
}

// drop instances from /org/sample/UserSession module
release(Usersession::class.moduleName)

3.10. Conflicting module declaration

When resoling a definition, if two definitions are defining the same type you will have a conflict. You can update your modules to change their paths. Or check about definition naming to specify a definition, a keep both definitions.

If two modules declare the same path, definitions will be in the same namespace.

4. Start the container

Koin is a DSL, a lightweight container and a pragmatic API. Once you have declared your definitions within Koin modules, your are ready to start the Koi container.

4.1. The startKoin function

The startKoin() function is the main entry point to launch Koin container. It need a list of Koin modules to run. Modules are loaded and definitions are ready to be resolved by the Koin container.

Starting Koin
startKoin(listOf(module1,module2 ...))

Once startKoin has been called, Koin will read all your modules & definitions. Koin is then ready for any get() or by inject() call to retrieve the needed instance.

At start you can also specify several options:

  • logging - see logging section

  • properties loading (environment, koin.properties file, extra properties …​) - see properties section

The startKoin() can’t be called more than once. If you need several point to load modules, use the loadKoinModules function.

4.2. Behind the start

When we start Koin, we create a KoinContext instance in the StandAloneContext holder instance which allows us to be agnostic.

This KoinContext instance gather all components that forms the Koin container. This class is built by the Koin class, the Koin instance builder. The Koin class is responsible to read modules, properties and build a KoinContext instance. This instance is then stored in our StandAloneContext holder.

4.3. Loading modules without startKoin function

You can’t call the startKoin() function more than once. But you can use directly the loadKoinModules() functions.

This function is interesting for SDK makers who want to use Koin, because they don’t need to use the starKoin() function and just use the loadKoinModules at the start of their library.

Load Koin modules
loadKoinModules(module1,module2 ...)

4.4. Stop Koin - closing all resources

You can close all the Koin resources and drop instances & definitions. For this you can use the stopKoin() function from anywhere.

5. Logging Koin activity

Koin has a simple logging API to log any Koin activity (allocation, lookup …​). The logging API is represented by the interface below:

Koin Logger
interface Logger {
    /**
     * Normal log
     */
    fun log(msg : String)

    /**
     * Debug log
     */
    fun debug(msg : String)

    /**
     * Error log
     */
    fun err(msg : String)
}

5.1. Set logging at start

By default, koin-core logs directly into default console output. But you can specify a logger parameter to setup your own Logger implementation:

startKoin(listof(...), logger = EmptyLogger())

5.2. Loggers implementation

Koin proposes some implementation of logging, in function of the target platform:

  • PrintLogger - directly log into console (included in koin-core)

  • EmptyLogger - log nothing (included in koin-core)

  • SLF4JLogger - Log with SLF4J. Used by ktor and spark (koin-logger-slf4j project)

  • AndroidLogger - log into Android Logger (included in koin-android)

5.3. Your own Logger

If you don’t want to log any info, you can use the EmptyLogger class.

You can easily implement the Logger interface to adapt to your logging technology.

6. Properties

Koin can handle properties from environment or external property file, to help you inject values into your definitions.

6.1. Loading properties at start

You can load several type of properties at start:

  • environment properties - load system properties

  • koin.properties file - load properties from /src/main/resources/koin.properties file

  • "extra" start properties - map of values passed at startKoin function

6.2. Read property from a module

In a Koin module, you can get a property by its key:

in /src/main/resoucres/koin.propertie file
// Key - value
server_url=http://service_url

Just load it with getProperty function:

val myModule = module {

    // use the "server_url" key to retrieve its value
    single { MyService(getProperty("server_url")) }
}

6.3. Read/Write property from a KoinComponent

If your class is a KoinComponent, you can interact with Koin properties directly from it with getProperty() and setProperty:

class MyComponent : KoinComponent {

    fun doSomething(){

        // Read a Koin property
        val serviceUrl = getProperty("server_url")

        // Set a Koin property
        setProperty("isDebug",false)
    }
}

7. Koin Components

Koin is a DSL to help describe your modules & definitions, a container to make definition resolution. What we need now is an API to retrieve our instances outside of the container. That’s the goal of Koin components.

7.1. Create a Koin Component

To give a class the capacity to use Koin features, we need to tag it with KoinComponent interface. Let’s take an example.

A module to define MyService instance
class MyService

val myModule = module {
    // Define a singleton for MyService
    single { MyService() }
}

we start Koin before using definition.

Start Koin with myModule
fun main(vararg args : String){
    // Start Koin
    startKoin(listOf(myModule))

    // Create MyComponent instance and inject from Koin container
    MyComponent()
}

Here is how we can write our MyComponent to retrieve instances from Koin container.

Use get() & by inject() to inject MyService instance
class MyComponent : KoinComponent {

    // lazy inject Koin instance
    val myService : MyService by inject()

    // or
    // eager inject Koin instance
    val myService : MyService get()
}

7.2. Unlock the Koin API with KoinComponents

Once you have tagged your class as KoinComponent, you gain access to:

  • by inject() - lazy evaluated instance from Koin container

  • get() - eager fetch instance from Koin container

  • release() - release module’s instances from its path

  • getProperty()/setProperty() - get/set property

7.3. Retrieving definitions with get & inject

Koin offers two ways of retrieving instances from the Koin container:

  • val t : T by inject() - lazy evaluated delegated instance

  • val t : T = get() - eager access for instance

// is lazy evaluated
val myService : MyService by inject()

// retrieve directly the instance
val myService : MyService get()

The lazy inject form is better to define property that need lazy evaluation.

7.4. Resolving instance from a name or a module

If you need to filter about visibility (search a definition by name or module), you can specify the following parameter with get() or by inject()

  • name - name of the definition (when specified name parameter in your definition)

  • module - path of the module

Example of module using classes names as modules:

val module = module {
    module(ComponentB::class.moduleName) {
        single { ComponentA() }
        single { ComponentB(get()) }
    }

    module(ComponentC::class.moduleName) {
        single { ComponentA() }
        single { ComponentC(get()) }
    }
}

class ComponentA
class ComponentB(val componentA: ComponentA)
class ComponentC(val componentA: ComponentA)

We can make the following resolutions:

// retrieve from given module
val a_b = get<ComponentA>(module = ComponentB::class.moduleName)
val a_c = get<ComponentA>(module = ComponentC::class.moduleName)

7.5. No inject() or get() in your API?

If your are using an API and want to use Koin inside it, just tag the desired class with KoinComponent interface.

7.6. Releasing instances

From a KoinComponent you can use the release() function to drop instances for a given module path:

val module = module {
    module("ComponentB") {
        single { ComponentA() }
        single { ComponentB(get()) }
    }
}

// Will release all instances from /ComponentB
release("ComponentB")

7.6.1. release() vs closeKoin()

The release() function release instances for a given module path. The closeKoin() function drop every thing in Koin, from definitions to instances.

8. Injection parameters

In any single or factory definition, you can use injection parameters: parameters that will be injected and used by your definition:

8.1. Defining an injection parameter

Below is an example of injection parameters. We established that we need a view parameter to build of Presenter class:

class Presenter(val view : View)

val myModule = module {
    single{ view : View -> Presenter(view) }
}

8.2. Injecting with values

In contrary to resolved dependencies (resolved with with get()), injection parameters are parameters passed through the resolution API. This means that those parameters are values passed with get() and by inject(), with the parametersOf() function:

class MyComponent : View, KoinComponent {

    // inject this as View value
    val presenter : Presenter by inject { parametersOf(this) }
}

8.3. Multiple parameters

If we want to have multiple parameters in our definition, we can use the destructured declaration to list our parameters:

class Presenter(val view : View, id : String)

val myModule = module {
    single{ (view : View, id : String) -> Presenter(view,id) }
}

In a KoinComponent, just use the parametersOf function with your arguments like below:

class MyComponent : View, KoinComponent {

    val id : String ...

    // inject with view & id
    val presenter : Presenter by inject { parametersOf(this,id) }
}

9. Extending Koin

Koin is an agnostic dependency injection framework, offering a DSL, a lightweight container and a simple API. To suit your favorite SDK/runtime technology, just extend it with some Kotlin extensions and bring Koin features.

9.1. Extend the DSL

The DSL is mainly defined in org.koin.dsl.context.ModuleDefinition class. Just make a Kotlin extension on type ModuleDefinition to make a new keyword.

Below, an example of viewModel keyword for koin-android-viewmodel.

viewModel keyword example
inline fun <reified T : ViewModel> ModuleDefinition.viewModel(
    name: String = "",
    noinline definition: Definition<T>
) { ... }

9.2. Bring Koin to your SDK

You can extend your actual Runtime/SDK to add Kotlin extension, to bring KoinComponent powers. Check the org.koin.standalone.KoinComponent.kt file to see all the existing Koin features available to port.

Below, an example of by inject() function ported to Android world (extension on ComponentCallbacks to be usable by Activity, Fragment and Service).

Koin inject function for Android
inline fun <reified T> ComponentCallbacks.inject(
    name: String = "",
    module: String? = null,
    noinline parameters: ParameterDefinition = emptyParameterDefinition()
) = lazy { (StandAloneContext.koinContext as KoinContext).get<T>(name, module, parameters) }

9.3. Need a new startKoin?

If you need to adapt the startKoin() function to your SDK, up to you to make a new one and call the original startKoin behind it.

Below, the startKoin() adapted for Android world (extension on Application class).

startKoin from Android Application class
fun Application.startKoin(
    application: Application,
    modules: List<Module>,
    extraProperties: Map<String, Any> = HashMap(),
    loadProperties: Boolean = true,
    logger: Logger = AndroidLogger()
) {
    Koin.logger = logger
    val koin = StandAloneContext.startKoin(
        modules,
        extraProperties = extraProperties,
        useKoinPropertiesFile = false
    ).with(application)
    if (loadProperties) koin.bindAndroidProperties(application)
}

10. Examples

Here are some koin-core example projects.

10.1. The Coffee Maker

You can find the project in 'examples/coffee-maker' folder.

10.1.1. The App Components

The coffee-maker is a pure Kotlin app for demoing dependency injection with Koin. This app is composed of:

  • CoffeeApp - retrieve and run the CoffeeMaker class

class CoffeeApp : KoinComponent, Runnable {

    val coffeeMaker: CoffeeMaker by inject()

    override fun run() {
        coffeeMaker.brew()
    }
}
  • CoffeeMaker - is composed of a Pump and a lazy injected Heater. Make the bre processing.

class CoffeeMaker(val pump: Pump) : KoinComponent {

    // Don't want to create a possibly costly heater until we need it.
    val heater: Heater by inject()

    fun brew() {
        heater.on()
        pump.pump()
        println(" [_]P coffee! [_]P ")
        heater.off()
    }
}
  • ElectricHeater - is a implementation of Heater

interface Heater {
    fun on()
    fun off()
    fun isHot() : Boolean
}

class ElectricHeater : Heater {

    var heating: Boolean = false

    override fun on() {
        println("~ ~ ~ heating ~ ~ ~")
        heating = true
    }

    override fun off() {
        heating = false
    }

    override fun isHot(): Boolean = heating
}
  • Thermosiphon - is a implmentation of Pump using a Heater

interface Pump {
    fun pump()
}

class Thermosiphon(val heater: Heater) : Pump{
    override fun pump() {
        if (heater.isHot()){
            println("=> => pumping => =>")
        }
    }
}

10.1.2. Assembling and running with Koin

We need to declare and assemble components:

  • CoffeeMakerPump & Heater (lazy)

  • Thermosiphon as Pump → Heater

  • ElectricHeater as Heater

Here is how we assemble it with Koin:

val coffeeMakerModule = module {
    single { CoffeeMaker(get()) }
    single { ElectricHeater() as Heater }
    single { Thermosiphon(get()) as Pump }
}

CoffeeMaker is a KoinComponent to lazy inject Heater.

CoffeeApp is a KoinComponent to lazy inject CoffeeMaker. This class is also outside of the Koin container.

Just need to start Koin with coffeeMakerModule module and run the CoffeeApp class.

fun main(vararg args: String) {

    startKoin(listOf(coffeeMakerModule))

    CoffeeApp().run()
}

Testing with Koin

Arnaud Giuliani <arnaud.g@insert-koin.io> :revnumber: 1.0.0-beta-6 :example-caption!:

Covers Koin testing features.

11. About koin-test

The koin-test project is dedicated to help you making JUnit test with Koin container and API.

11.1. Gradle setup

Add the koin-android dependency to your Gradle project:

// Add Jcenter to your repositories if needed
repositories {
    jcenter()
}
dependencies {
    // Testing with Koin
    testCompile 'org.koin:koin-test:1.0.0-beta-6'
}

12. Making your test a KoinComponent with KoinTest

By tagging your class KoinTest, your class become a KoinComponent and bring you:

  • by inject() & get() - function to retrieve yoru instances from Koin

  • check & dryRun - help you hceck your configuration

  • declareMock & declare - to declare a mock or a new definition in the current context

class ComponentA
class ComponentB(val a: ComponentA)

class MyTest : KoinTest {

    // Lazy inject property
    val componentB : ComponentB by inject()

    @Test
    fun `should inject my components`() {
        startKoin(listOf(module {
            single { ComponentA() }
            single { ComponentB(get()) }
        }))

        // directly request an instance
        val componentA = get<ComponentA>()

        assertNotNull(a)
        assertEquals(componentA, componentB.a)
    }

Don’t hesitate to overload Koin modules configuration to help you partly build your app.

13. Mocking out of the box

Instead of making a new module each time you need a mock, you can declare a mock on the fly with declareMock:

class MyTest : KoinTest {

    class ComponentA
    class ComponentB(val a: ComponentA)

    @Test
    fun `should inject my components`() {
        startKoin(listOf(module {
            single { ComponentA() }
            single { ComponentB(get()) }
        }))
        declareMock<ComponentA>()

        // retrieve mock
        assertNotNull(get<ComponentA>())

        // is built with mocked ComponentA
        assertNotNull(get<ComponentB>())
    }

declareMock can specify if you want a single or factory, and if you wan to have it in a module path.

14. Declaring a component on the fly

When a mock is not enough and don’t want to create a module just for this, you can use declare:

    @Test
    fun `successful declare an expression mock`() {
        startKoin(listOf())

        declare { factory { ComponentA("Test Params") } }

        Assert.assertNotEquals(get<ComponentA>(), get<ComponentA>())
    }

15. Checking your Koin configuration

Koin offers several ways to test if you Koin modules are good:

  • check - walk through your definition tree and check if each definition is bound

  • dryRun - run all your modules and try to instantiate each definition

    val MVPModule = module {
        single { Repository(get()) }

        module("view") {
            single { View() }
            single { Presenter(get§)) }
        }
    }

    val DataSourceModule = module {
        single { DebugDatasource() } bind Datasource::class
    }

    @Test
    fun `check MVP hierarchy`() {
        // Check the Koin modules binding
        check(listOf(MVPModule, DataSourceModule))
    }

    @Test
    fun `dry run MVP hierarchy`() {
        // Run each definition
        startKoin(listOf(MVPModule, DataSourceModule))
        dryRun()
    }

check and dryRun run your entire module definitions. DryRun will ty to make each definition. It’s sometimes not possible to run your configuration in JUnit (Android context needed). Then use the check configuration.

Advanced core features

Arnaud Giuliani <arnaud.g@insert-koin.io> :revnumber: 1.0.0-beta-6 :example-caption!:

Covers Koin side features with reflection.

16. About koin-reflect

The koin-reflect project is dedicated to side features which use java reflection.

16.1. Gradle setup

Add the koin-reflect dependency to your Gradle project:

// Add Jcenter to your repositories if needed
repositories {
    jcenter()
}
dependencies {
    // Reflection features for Koin
    compile 'org.koin:koin-reflect:1.0.0-beta-6'
}

17. DSL enhancements

Below are some DSL related features.

17.1. Easily build your definition

It’s possible to avoid inject your definition using get() in your definitions to simplify your writing. The build() function will help you build your component by taking it’s first constructor and injecting dependencies.

Below you can compare the two ways of injecting inside definitions:

    class ComponentA
    class ComponentB(val a: ComponentA)
    class ComponentC(val a: ComponentA, val b: ComponentB)

    // inject definitions with get()
    val noReflect = module {
           single { ComponentA() }
           single { ComponentB(get()) }
           single { ComponentC(get(),get()) }
       }

    // without get() but with build() instead
    val buildwithReflect = module {
           single { ComponentA() }
           single { build<ComponentB>() }
           single { build<ComponentC>() }
       }

Keep in mind that build() function is not just magic, it has also a very small cost (the time to take your constructor and inject each argument). If you care about performances, don’t hesitate to switch your definition writing between get or build to compare the their performances for you.

Features for Java developers

Arnaud Giuliani <arnaud.g@insert-koin.io> :revnumber: 1.0.0-beta-6 :example-caption!:

Covers Java Koin features.

18. About koin-java

The koin-java project is dedicated to bring enhancement features to help java developers.

18.1. Gradle setup

Add the koin-java dependency to your Gradle project:

// Add Jcenter to your repositories if needed
repositories {
    jcenter()
}
dependencies {
    // Koin Java features
    compile 'org.koin:koin-java:1.0.0-beta-6'
}

19. Starting a Koin module from Java

A Koin module still must be written in Kotlin, using the @JvmField to make it Java friendly:

@JvmField
val koinModule = module {

    single { ComponentA() }
}

Now just call the KoinJavaStarter.startKoin function from a Java class to start your module:

KoinJavaStarter.startKoin(singletonList(koinModule));

20. Injecting into a Java class

The koin-java project provides a static Java helper: KoinJavaComponent. This will help you make:

  • direct instance injection with get(Class)

ComponentA a = get(ComponentA.class);
  • lazy instance injection with inject(Class)

Lazy<ComponentA> lazy_a = inject(ComponentA.class);

Make sure to have the static import of class KoinJavaComponent to have a short syntax.

Koin for Android developers

Arnaud Giuliani <arnaud.g@insert-koin.io> :revnumber: 1.0.0-beta-6 :example-caption!:

Covers Koin features for Android.

21. About koin-android

The koin-android project is dedicated to provide Koin powers to Android world.

For general Koin concepts, check the koin-core manual.

21.1. Gradle setup

Add the koin-android dependency to your Gradle project:

// Add Jcenter to your repositories if needed
repositories {
    jcenter()
}
dependencies {
    // Koin for Android
    compile 'org.koin:koin-android:1.0.0-beta-6'
}

22. Start Koin with Android

The koin-android project is dedicated to provide Koin powers to Android world.

22.1. startKoin() from your Application

From your Application class you can use the startKoin() function:

class MainApplication : Application() {

    override fun onCreate() {
        super.onCreate()

        startKoin(this, myAppModules)
    }
}

22.2. Starting Koin with Android context from elsewhere?

If you need to start Koin from another Android class, you can use the startKoin() function and provide your Android Context instance with just like:

// Start Koin and init Application instance definition
startKoin(androidContext, myAppModules)

Also, there is a with operator that can help you bind the Context instance in the case of non Android class (Junit for example). It can help like follow:

Example of mocking Androic context in a JUnit
// Start Koin and init Context instance definition
startKoin(myAppModules) with (mock(Context::class.java))

22.3. Koin Logging

With the startKoin() function, we have a parameter logger which has a default value: AndroidLogger(). This logger is an Android implementation of the Koin logger.

Up to you to change this logger if it doesn’t suits to your needs.

Shut off Koin Logger
class MainApplication : Application() {

    override fun onCreate() {
        super.onCreate()

        startKoin(this, myAppModules, logger = EmptyLogger())
    }
}

22.4. Properties

You can use Koin properties in the assets/koin.properties file, to store keys/values. You can also use extraProperties at start:

Use Koin extra properties
// Shut off Koin Logger
class MainApplication : Application() {

    override fun onCreate() {
        super.onCreate()

        startKoin(this, myAppModules, extraProperties = mapOf( ... ))
    }
}

23. Retrieve your components from Koin

Once you have declared some modules and you have started Koin, how can you retrieve your instances in your Android Activity Fragments or Services?

23.1. Activity, Fragment & Service as KoinComponents

Activity, Fragment & Service are extended with the KoinComponents extension. You gain access to:

  • by inject() - lazy evaluated instance from Koin container

  • get() - eager fetch instance from Koin container

  • release() - release module’s instances from its path

  • getProperty()/setProperty() - get/set property

For a module that declares a 'presenter' component:

val androidModule = module {
    // a factory of Presenter
    factory { Presenter() }
}

We can declare a property as lazy injected:

Lazy inject a property
class DetailActivity : AppCompatActivity() {

    // Lazy injected Presenter instance
    override val presenter : Presenter by inject()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // ...
    }

Or we can just directly get an instance:

Get directly an instance
class DetailActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Retrieve a Presenter instance
        val presenter : Presenter = get()
    }

23.2. Need inject() and get() anywhere else?

If you need to inject() or get() an instance from another class, just tag it with KoinComponent interface.

24. Koin DSL Extension for Android

Below, the added keywords for Koin DSL.

24.1. Getting Android context inside a Module

The androidContext() function allows you to get the Context instance in a Koin module, to help you simply write expression that requires the Application instance.

val appModule = module {

    // create a Presenter instance with injection of R.string.mystring resources from Android
    factory {
        MyPresenter(androidContext().resources.getString(R.string.mystring))
    }
}

25. Scope features for Android

The koin-android-scope project is dedicated to bring Android scope features.

25.1. Gradle setup

Choose the koin-android-scope dependency to add to your Gradle project (android or androix version):

// Add Jcenter to your repositories if needed
repositories {
    jcenter()
}
dependencies {
    // Scope for Android
    compile 'org.koin:koin-android-scope:1.0.0-beta-6'
    // or Scope for AndroidX
    compile 'org.koin:koin-androidx-scope:1.0.0-beta-6'
}

25.2. Taming the Android lifecycle

Android components are mainly managed by their lifecycle: we can’t directly instantiate an Activity nor a Fragment. The system make all creation and management for us, and make callbacks on methods: onCreate, onStart…​

That’s why we can’t describe our Activity/Fragment/Service in a Koin module. We need then to inject dependencies into properties and also respect the lifecycle: Components related to the UI parts must be released on soon as we don’t need them anymore.

Then we have:

  • long live components (Services, Data Repository …​) - used by several screens, never dropped

  • medium live components (user sessions …​) - used by several screens, must be dropped after an amount of time

  • short live components (views) - used by only one screen & must be dropped at the end of the screen

Long live components can be easily described as single definitions. For medium and short live components we can have several approaches.

In the case of MVP architecture style, the Presenter is a short live component to help/support the UI. The presenter must be created each time the screen is showing, and dropped once the screen is gone.

A new Presenter is created each time
class DetailActivity : AppCompatActivity() {

    // injected Presenter
    override val presenter : Presenter by inject()

We can describe it in a module:

  • as factory - to produce a new instance each time the by inject() or get() is called

val androidModule = module {

    // Facvtory instance of Presenter
    factory { Presenter() }
}
  • as single & module - to produce an instance when called and manually drop it when needed

val androidModule = module {

    // a dedicated module for Presenter
    module("org.samples.detail"){
        single { Presenter() }
    }
}

// Call release() to release Presenter instance when needed
release("org.samples.detail")

Most of Android memory leaks comes from referencing a UI/Android component from a non Android component. Th system keeps a reference on it and can’t totally drop it via garbage collection.

25.3. Binding to lifecycle with scopedWith

Koin gives the scopedWith function to bind the actual Android component to listen its lifecycle and release instances of a module.

class DetailActivity : AppCompatActivity() {

    // Lazy injected Presenter instance
    override val presenter : Presenter by inject()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // on ON_DESTROY release org.samples.detail module instances
        scopedWith("org.samples.detail")
    }

When using the scopedWith function, you can specify the end signal on which you want to react: ON_DESTROY | ON_STOP

A little function to help you write your scoped module, you can use the moduleName property on a class to help declare your module:

val androidModule = module {

    // DetailActivity module for Presenter
    module(DetailActivity::class.moduleName){
        single { Presenter() }
    }
}

Then you can use the moduleName property to easily scope a component:

class DetailActivity : AppCompatActivity() {

    // Lazy injected Presenter instance
    override val presenter : Presenter by inject()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // on ON_DESTROY release DetailActivity module instances
        scopedWith(this::class.moduleName)
    }

26. Architecture Components with Koin: ViewModel

The koin-android-viewmodel project is dedicated to bring Android Architecture VieModel features.

26.1. Gradle setup

Choose the koin-android-viewmodel dependency to add to your Gradle project (android or androix version):

// Add Jcenter to your repositories if needed
repositories {
    jcenter()
}
dependencies {
    // ViewModel for Android
    compile 'org.koin:koin-android-viewmodel:1.0.0-beta-6'
    // or ViewModel for AndroidX
    compile 'org.koin:koin-androidx-viewmodel:1.0.0-beta-6'
}

26.2. ViewModel DSL

The koin-android-viewmodel introduces a new viewModel DSL keyword that comes in complement of single and `factory, to help declare a ViewModel component and bind it to an Android Component lifecycle.

val appModule = module {

    // ViewModel for Detail View
    viewModel { DetailViewModel(get(), get()) }
}

Your declared component must at least extends the android.arch.lifecycle.ViewModel class. You can specify how you inject the constructor of the class and use the get() function to inject dependencies.

The viewModel help declare a factory instance of ViewModel. This instance will be handled by internal ViewModelFactory and reattach ViewModel instance if needed.

The viewModel keyword can also let you use the injection parameters.

26.3. Injecting your ViewModel

To inject a ViewModel in an Activity, Fragment or Service use:

  • by viewModel() - lazy delegate property to inject a ViewModel into a property

  • getViewModel() - directly get the ViewModel instance

class DetailActivity : AppCompatActivity() {

    // Lazy inject ViewModel
    val viewModel: DetailViewModel by viewModel()
}

26.4. Shared ViewModel

One ViewModel instance can be shared between Fragments and their host Activity.

To inject a shared ViewModel in a Fragment use:

  • by sharedViewModel() - lazy delegate property to inject shared ViewModel instance into a property

  • getSharedViewModel() - directly get the shared ViewModel instance

Just declare the ViewModel only once:

val weatherAppModule = module {

    // WeatherViewModel declaration for Weather View components
    viewModel { WeatherViewModel(get(), get()) }
}

And reuse it in Activity and Fragments:

class WeatherActivity : AppCompatActivity() {

    /*
     * Declare WeatherViewModel with Koin and allow constructor dependency injection
     */
    private val viewModel by viewModel<WeatherViewModel>()
}

class WeatherHeaderFragment : Fragment() {

    /*
     * Declare shared WeatherViewModel with WeatherActivity
     */
    private val viewModel by sharedViewModel<WeatherViewModel>()
}

class WeatherListFragment : Fragment() {

    /*
     * Declare shared WeatherViewModel with WeatherActivity
     */
    private val viewModel by sharedViewModel<WeatherViewModel>()
}

The Activity sharing its ViewModel inject it with by viewModel() or getViewModel(). Fragments are reusing the shared ViewModel with by sharedViewModel().

26.5. ViewModel and injection parameters

the viewModel keyword and injection API is compatible with injection parameters.

In the module:

val appModule = module {

    // ViewModel for Detail View with id as parameter injection
    viewModel { (id : String) -> DetailViewModel(id, get(), get()) }
}

From the injection call site:

class DetailActivity : AppCompatActivity() {

    val id : String // id of the view

    // Lazy inject ViewModel with id parameter
    val viewModel: DetailViewModel by viewModel{ parametersOf(id)}
}

Koin for Spark web framework

Arnaud Giuliani <arnaud.g@insert-koin.io> :revnumber: 1.0.0-beta-6 :example-caption!:

Covers Koin features for Spark project.

27. About koin-spark

The koin-spark project is dedicated to bring dependency injection for Spark.

27.1. Gradle setup

Add the koin-ktor dependency to your Gradle project:

// Add Jcenter to your repositories if needed
repositories {
    jcenter()
}
dependencies {
    // Reflection features for Koin
    compile 'org.koin:koin-spark:1.0.0-beta-6'
}

27.2. DSL enhancements

The project brings the controller keyword to declare a single instance of your controller:

val helloAppModule = module {
    single { HelloServiceImpl(get()) as HelloService }
    single { HelloRepositoryImpl() as HelloRepository }

    // Declare a controller
    controller { HelloController(get()) }
}

You controller class must extend SparkController interface:

class HelloController(val service: HelloService) : SparkController {
    init {
        get("/hello") {
            service.sayHello()
        }
    }
}

27.3. Spark & Koin

The koin-spark project also introduces start() and stop() function. This first help you start Spark server with Koin modules:

fun main(vararg args: String) {
    // Spark with Koin
    start(modules = listOf(helloAppModule))
}

Koin for Ktor framework

Arnaud Giuliani <arnaud.g@insert-koin.io> :revnumber: 1.0.0-beta-6 :example-caption!:

Covers Koin features for Spark project.

28. About koin-ktor

The koin-ktor project is dedicated to bring dependency injection for Ktor.

28.1. Gradle setup

Add the koin-android dependency to your Gradle project:

// Add Jcenter to your repositories if needed
repositories {
    jcenter()
}
dependencies {
    // Reflection features for Koin
    compile 'org.koin:koin-ktor:1.0.0-beta-6'
}

28.2. Install Koin & inject

To start Koin container, use the installKoin() starter function:

fun Application.main() {
    // Install Ktor features
    install(DefaultHeaders)
    install(CallLogging)
    installKoin(listOf(helloAppModule), logger = SLF4JLogger())

    //...
}

You can also start it from outside of Ktor, but you won’t be compatible with autoreload feature.

KoinComponent powers are available from Application class:

fun Application.main() {
    //...

    // Lazy inject HelloService
    val service by inject<HelloService>()

    // Routing section
    routing {
        get("/hello") {
            call.respondText(service.sayHello())
        }
    }
}

From Routing class:

fun Application.main() {
    //...

    // Lazy inject HelloService
    val service by inject<HelloService>()

    // Routing section
    routing {
        v1()
    }
}

fun Routing.v1() {

    // Lazy inject HelloService from within a Ktor Routing Node
    val service by inject<HelloService>()

    get("/v1/hello") {
        call.respondText("[/v1/hello] " + service.sayHello())
    }
}

From Route class:

fun Application.main() {
    //...

    // Lazy inject HelloService
    val service by inject<HelloService>()

    // Routing section
    routing {
        v1()
    }
}

fun Routing.v1() {
    hello()
}

fun Route.hello() {

    // Lazy inject HelloService from within a Ktor Route
    val service by inject<HelloService>()

    get("/v1/bye") {
        call.respondText("[/v1/bye] " + service.sayHello())
    }
}