This document covers the all koin projects.
Koin core: DSL, Container & API
Arnaud Giuliani <arnaud.g@insert-koin.io> - and on twitter: @arnogiu :revnumber: 1.0.1
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.1'
}
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 asbean
) -
get
- resolve a component dependency -
bind
- add type to bind for given bean definition
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() }
}
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() }
}
This 2nd way of style declaration is prefered and will be used for the rest of the documentation. |
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
By default, Koin will name your definition depending on modules path & class name.
module {
single { ComponentA() }
module("A") {
single { ComponentB() }
}
}
The name of ComponentA
definition will be ComponentA
, and for ComponentB
you will have A.ComponentB
.
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<Service>("default") { ServiceImpl() }
single<Service>("test") { ServiceImpl() }
}
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<Service>{ ServiceImpl() }
}
module("org.sample"){
single<Service>{ ServiceImpl() }
}
}
// 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.
val myModuleA = module {
single<Service> { ServiceImp() }
}
val myModuleB = module {
// eager creation for this definition
single<Service>(createOnStart=true) { TestServiceImp() }
}
val myModuleA = module {
single<Service> { ServiceImp() }
}
val myModuleB = module(createOnStart=true) {
single<Service>{ TestServiceImp() }
}
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:
val myModuleA = module {
single<Service> { ServiceImp() }
}
val myModuleB = module {
single<Service> { TestServiceImp() }
}
// Will throw an BeanOverrideException
startKoin(listOf(myModuleA,myModuleB))
To allow definition overriding, you have to use the override
parameter:
val myModuleA = module {
single<Service> { ServiceImp() }
}
val myModuleB = module {
// override for this definition
single<Service>(override=true) { TestServiceImp() }
}
val myModuleA = module {
single<Service> { ServiceImp() }
}
// Allow override for all definitions from module
val myModuleB = module(override=true) {
single<Service> { TestServiceImp() }
}
Order matters when listing modules and overriding definitions. You must have your overriding definitions in last of your module list. |
2.12.3. Dealing with generics
Koin definitions doesn’t take in accounts generics type argument. For example, the module below tries to define 2 definitions of List:
module {
single { ArrayList<Int>() }
single { ArrayList<String>() }
}
Koin won’t start with such definitions, understanding that you want to override one definition for the other.
To allow you, use the 2 definitions you will have to differentiate them via their name, or location (module). For example:
module {
single(name="Ints") { ArrayList<Int>() }
single(name="Strings") { ArrayList<String>() }
}
2.13. Experimental features
2.13.1. Create instances using the create() function
Koin has the create<T>()
function that allows you to create an instance of type T
and inject its first constructor.
Note that if you don"t provide any definition lambda to your single/factory definition, will be able to create it for you:
single<T>() == single { create<T>() }
factory<T>() == factory { create<T>() }
When you need to specify the projected type:
single<T> { R() } == single<T> { create<R>() }
factory<T>() { R() } == factory<T> { create<R>() }
This help write definition without having to fill manually the constructor with get()
functions. Compare from the 'manual'
to the 'automated' created instances.
// 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> { ControllerImpl(get()) }
}
// 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> { create<ControllerImpl>() }
}
3. Modules & namespaces
By using Koin, you describe definitions in modules. In this section we will see how to declare, organize & link your modules. A module represents a namespace, a way of avoid name conflicts and has some visibility rules.
3.1. What is a 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.
// 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:
// 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.
// 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.
val sampleModule = module("org.sample") { ... }
// is equivalent to
val sampleModule = module {
module("org") {
module("sample") {
// ...
}
}
}
3.4. Implicit definitions naming
For a given definition, you can either give a name (with the name
attribute), or let Koin give a name for you. Let’s take a simple module example:
module {
module("B") {
single { ComponentA() }
single { ComponentB(get()) }
}
module("C") {
single { ComponentA() }
single { ComponentC(get()) }
}
}
If you try to resolve ComponentA
directly, Koin won’t understand which definition you need to use. Koin gives a default name to each definition, prefixing it with modules path.
From the module above, Koin will start like that:
[module] declare Single [name='B.ComponentA',class='org.koin.test.module.ImplicitNamingTest.ComponentA', path:'B']
[module] declare Single [name='B.ComponentB',class='org.koin.test.module.ImplicitNamingTest.ComponentB', path:'B']
[module] declare Single [name='C.ComponentA',class='org.koin.test.module.ImplicitNamingTest.ComponentA', path:'C']
[module] declare Single [name='C.ComponentC',class='org.koin.test.module.ImplicitNamingTest.ComponentC', path:'C']
You can directly request ComponentA
from 2 paths: B.ComponentA
or C.ComponentA
. Just use the name
value when requesting your instance:
get<ComponentA>(name = "B.ComponentA")
3.5. 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.6. Linking modules strategies
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<Datasource> { LocalDatasource() }
}
val remoteDatasourceModule = module {
single<Datasource> { RemoteDatasource() }
}
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.7. Visibility rules
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 seeComponentB
,ComponentC
& `ComponentD) -
ComponentB
can see definitions from org & root (can see/
and/org
- can resolve ComponentA - can’t seeComponentC
& `ComponentD ) -
ComponentC
can see definitions from sample, org & root (can see/
,/org
,/org/sample
- can resolveComponentA
,ComponentB
- can’t seeComponentD
) -
ComponentD
can see definitions from demo, org & root (can see/
,/org
,/org/demo
- can resolve ComponentA`,ComponentB
- can’t seeComponentC
)
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.
3.8. (Deprecated) Releasing instances from a path
From a KoinComponent, you can the release()
function to release single instances for a given path. For example;
module {
module(path = "A") {
single { ComponentA() }
module(path = "B") {
single { ComponentB() }
module(path = "C") {
single { ComponentC() }
}
}
}
}
-
release("A"): will release ComponentA, ComponentB & ComponentC
-
release("B"): will release ComponentB & ComponentC
-
release("C"): will release ComponentC
This API is deprecated. In next version, Koin won’t allow to drop single. Better use the Scope API |
4. Using Scopes
Koin brings a simple API to let you define instances that are tied to a limit lifetime.
4.1. What is a scope?
Scope is a fixed duration of time or method calls in which an object exists. Another way to look at this is to think of scope as the amount of time an object’s state persists. When the scope context ends, any objects bound under that scope cannot be injected again (they are dropped from the container).
4.2. Scope definition
By default in Koin, we have 3 kind of scopes:
-
single
definition, create an object that persistent with the entire container lifetime (can’t be dropped). -
factory
definition, create a new object each time. Short live. No persistence in the container (can’t be shared). -
scope
definition, create an object that persistent tied to the associated scope lifetime.
To declare a scope definition, use the scope
function with a given scope Id:
module {
scope("scope_id") { Presenter() }
}
A scope definition can’t be resolved if associated scope has not been created. Then |
4.3. Create & retrieve a scope
From a KoinComponent
class, just use the getKoin()
function to have access to following functions
-
createScope(id : String)
- create a scope with given id in the Koin scope registry -
getScope(id : String)
- retrieve a previously created scope with given id, from the Koin scope registry -
getOrCreateScope(id : String)
- create or retrieve if already created, the scope with given id in the Koin scope registry
4.4. Using a scope
Once you have create a Scope
instance, let use it to resolve a component:
module {
scope("scope_id") { Presenter() }
}
// create a scope
val session = getKoin().createScope("scope_id")
// or get scope if already created before
val session = getKoin().getScope("scope_id")
// will return the same instance of Presenter until Scope 'scope_id' is closed
val presenter = get<Presenter>()
Just resolving it: by inject()
or get()
. The DSL will indicate which scope to use.
4.5. Inject it with DSL
If one of your definition need to inject a scope instance, just resolve it … but be sure to have created the scope before:
class Presenter(val userSession : UserSession)
Just inject it into constructor, with the right scope:
module {
// Shared user session data
scope { UserSession() }
// Inject UserSession instance from "session" Scope
factory { Presenter(get())}
}
4.6. Closing a scope
Once your scope is finished, just closed it with the Scope.close()
function:
// from a KoinComponent
val session = getKoin().createScope("session")
// will return the same instance of Presenter until 'session' is closed
val presenter = get<Presenter>()
// close it
session.close()
// instance of presenter has been dropped
Beware that you can’t inject instances anymore from a closed scope. |
4.7. Scope closing callback
It’s also possible to listen scope closing. Just use the registerScopeCallback()
function to register a ScopeCallback
instance.
This will notify your ScopeCallback
on the onClose(id: String)
function, when a scope has been closed.
registerScopeCallback(object : ScopeCallback{
override fun onClose(id: String) {
// scope id has been closed
}
})
5. 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.
5.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.
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 |
5.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.
5.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.
loadKoinModules(module1,module2 ...)
5.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.
6. 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:
interface Logger {
/**
* Normal log
*/
fun log(msg : String)
/**
* Debug log
*/
fun debug(msg : String)
/**
* Error log
*/
fun err(msg : String)
}
6.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())
6.2. Loggers implementation
Koin proposes some implementation of logging, in function of the target platform:
-
PrintLogger
- directly log into console (included inkoin-core
) -
EmptyLogger
- log nothing (included inkoin-core
) -
SLF4JLogger
- Log with SLF4J. Used by ktor and spark (koin-logger-slf4j
project) -
AndroidLogger
- log into Android Logger (included inkoin-android
)
6.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.
7. Properties
Koin can handle properties from environment or external property file, to help you inject values into your definitions.
7.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
7.2. Read property from a module
In a Koin module, you can get a property by its key:
// 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")) }
}
7.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)
}
}
8. 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.
8.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.
class MyService
val myModule = module {
// Define a singleton for MyService
single { MyService() }
}
we start Koin before using definition.
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.
class MyComponent : KoinComponent {
// lazy inject Koin instance
val myService : MyService by inject()
// or
// eager inject Koin instance
val myService : MyService get()
}
8.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
8.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. |
8.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)
8.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.
8.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")
8.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.
9. Injection parameters
In any single
or factory
definition, you can use injection parameters: parameters that will be injected and used by your definition:
9.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) }
}
9.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) }
}
9.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) }
}
10. 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.
10.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
.
inline fun <reified T : ViewModel> ModuleDefinition.viewModel(
name: String = "",
noinline definition: Definition<T>
) { ... }
10.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
).
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) }
10.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).
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)
}
11. Examples
Here are some koin-core example projects.
11.1. The Coffee Maker
You can find the project in 'examples/coffee-maker' folder.
11.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 theCoffeeMaker
class
class CoffeeApp : KoinComponent, Runnable {
val coffeeMaker: CoffeeMaker by inject()
override fun run() {
coffeeMaker.brew()
}
}
-
CoffeeMaker
- is composed of aPump
and a lazy injectedHeater
. Make the bre processing.
class CoffeeMaker(val pump: Pump, val lazyHeater: Lazy<Heater>) {
// Don't want to create a possibly costly heater until we need it.
val heater: Heater by lazy { lazyHeater.value }
fun brew() {
heater.on()
pump.pump()
println(" [_]P coffee! [_]P ")
heater.off()
}
}
-
ElectricHeater
- is a implementation ofHeater
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 implementation of Pump using aHeater
interface Pump {
fun pump()
}
class Thermosiphon(val heater: Heater) : Pump{
override fun pump() {
if (heater.isHot()){
println("=> => pumping => =>")
}
}
}
11.1.2. Assembling and running with Koin
We need to declare and assemble components:
-
CoffeeMaker
→Pump
&Heater
(lazy) -
Thermosiphon
as Pump →Heater
-
ElectricHeater
as Heater
Here is how we assemble it with Koin:
val coffeeMakerModule = module {
single { CoffeeMaker(get(), lazy { get<Heater>() }) }
single<Pump> { Thermosiphon(get()) }
single<Heater> { ElectricHeater() }
}
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> - and on twitter: @arnogiu :revnumber: 1.0.1 :example-caption!:
Covers Koin testing features.
12. About koin-test
The koin-test
project is dedicated to help you making JUnit test with Koin container and API.
12.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.1'
}
13. 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. |
14. 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. |
15. 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>())
}
16. Checking your Koin configuration
Koin offers a way to test if you Koin modules are good: checkModules
- walk through your definition tree and check if each definition is bound
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
checkModules(listOf(MVPModule, DataSourceModule))
}
Koin Core Ext
Arnaud Giuliani <arnaud.g@insert-koin.io> - and on twitter: @arnogiu :revnumber: 1.0.1
Cover the extended features for koin-core project.
17. Extensions & Experimental features
The koin-core-ext
brings extensions & experimental features to Koin.
17.1. Better definition declaration (Experimental)
Koin DSL can be seen as "manual", while you must fill constructors with "get()" function to resolve needed instances. When your definition don’t need any special constructor integration (injection paarameters or special scope Id), we can go with more compact writing style thanks to API below.
Using reflection is not costless. it replaces what you don"t want to write with reflection code (finding primary constructors, injecting parameters…). Mind it before using it, if you are on performances constraints platform (Android for example) |
17.2. Build any instance with create()
The first introduced function is the create()
function.
Instead of declaring a definition with instantiating its constructor and retrieving instances with get()
module {
single { ComponentA(get() ...) }
}
You can use instead, the create()
function to build an instance from its primary constructor, and fill the needed dependencies.
module {
single { create<ComponentA>() }
}
17.3. Even more simple definitions
You can also use the more "compact" notation that will use the create()
function. Just use the single function without any expression:
module {
single<ComponentA>()
}
If you have an implementation type and want to resolve with a target type, you can use the following singleBy
function:
module {
singleBy<Target,Implementation>()
}
Works for single, factory & scope |
If you use custom constructors expression like injection parameters or others, don’t use the reflection API. |
Features for Java developers
Arnaud Giuliani <arnaud.g@insert-koin.io> - and on twitter: @arnogiu :revnumber: 1.0.1 :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
implementation 'org.koin:koin-java:1.0.1'
}
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 |
Koin for Android developers
Arnaud Giuliani <arnaud.g@insert-koin.io> - and on twitter: @arnogiu :revnumber: 1.0.1 :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
implementation 'org.koin:koin-android:1.0.1'
}
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:
// 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.
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:
// 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:
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:
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 to the existing Scope API.
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
implementation 'org.koin:koin-android-scope:1.0.1'
// or Scope for AndroidX
implementation 'org.koin:koin-androidx-scope:1.0.1'
}
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.
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 theby inject()
orget()
is called
val androidModule = module {
// Factory instance of Presenter
factory { Presenter() }
}
-
as
scope
- to produce an instance tied to a scope
val androidModule = module {
scope("scope_id") { Presenter() }
}
Most of Android memory leaks comes from referencing a UI/Android component from a non Android component. The system keeps a reference on it and can’t totally drop it via garbage collection. |
25.3. Binding scope to lifecycle
Koin gives the bindScope
function to bind the actual Android component lifecycle, to a given scope. On lifecycle’s end, this will close the bound scope.
class MyActivity : AppCompatActivity() {
// inject Presenter instance, tied to current "scope_id" scope
val presenter : Presenter by inject()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// bind current lifecycle to Activity's scope
bindScope(getScope("scope_id"))
}
25.4. Sharing instances between components with scopes
In a more extended usage, you can use a Scope
instance across components. For example, if we need to share a UserSession
instance.
First declare a scope definition:
module {
// Shared user session data
scope("session") { UserSession() }
}
When needed to begin use a UserSession
instance, create a scope for it:
getKoin().createScope("session")
Then use it anywhere you need it:
class MyActivity1 : AppCompatActivity() {
// inject Presenter instance, tied to current MyActivity's scope
val userSession : UserSession by inject()
}
class MyActivity2 : AppCompatActivity() {
// inject Presenter instance, tied to current MyActivity's scope
val userSession : UserSession by inject()
}
or you can also inject it with Koin DSL. If a presenter need it:
class Presenter(val userSession : UserSession)
Just inject it into constructor, with the right scope:
module {
// Shared user session data
scope("session") { UserSession() }
// Inject UserSession instance from "session" Scope
factory { Presenter(get())}
}
When you have to finish with your scope, just close it:
val session = getKoin().getScope("session")
session.close()
26. Architecture Components with Koin: ViewModel
The koin-android-viewmodel
project is dedicated to bring Android Architecture ViewModel 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
implementation 'org.koin:koin-android-viewmodel:1.0.1'
// or ViewModel for AndroidX
implementation 'org.koin:koin-androidx-viewmodel:1.0.1'
}
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()) }
// or
viewModel<DetailViewModel>()
}
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 |
The |
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()) }
// or
viewModel<WeatherViewModel>()
}
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 injects it with |
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> - and on twitter: @arnogiu :revnumber: 1.0.1 :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.1'
}
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> - and on twitter: @arnogiu :revnumber: 1.0.1 :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.1'
}
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 |
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())
}
}