Core documentation
Introduction
Description
Kodein is a very useful dependency injection / retrieval container, it is very easy to use and configure.
-
Lazily instantiate your dependencies when needed.
-
Stop caring about dependency initialization order.
-
Easily bind classes or interfaces to their instance, provider or factory.
-
Easily debug your dependency bindings and recursions.
-
It is small, fast and optimized (makes extensive use of
inline
). -
It proposes a very simple and readable declarative DSL.
-
It is not subject to type erasure (like Java).
-
It integrates nicely with Android.
-
It proposes a very kotlin-esque idiomatic API.
-
It can be used in plain Java.
Example
Kodein makes it very easy to bind a type:
val kodein = Kodein {
bind<Dice>() with provider { RandomDice(0, 5) }
bind<DataSource>() with singleton { SqliteDS.open("path/to/file") }
}
Once bindings are declared, Kodein allows you to either inject or retrieve the dependencies for a class.
If you want your class to be unaware of dependency retrieval, then you can inject dependencies at construction:
class Controller(private val ds: DataSource) {
/*...*/
}
val controller by kodein.newInstance { Controller(instance()) }
If you want your class to handle it’s dependencies by itself, then you can have it easily retrieve them:
class Controller(override val kodein: Kodein): KodeinAware {
private val ds: DataSource by instance()
}
Platform compatibility & Genericity
Kodein is compatible with all platforms that the Kotlin language compiles to: JVM & compatible (Android), Javascript and all the Kotlin/Native targets.
On the JVM & compatible (Android), you need to choose to use either the erased
or the generic
version.
On the Javascript and Native targets, only the erased
version is available.
The difference is very simple: the generic
version is NOT subject to type erasure while the erased
version IS.
Of course, it is a little bit more complicated!
To be able to circumvent the type erasure that’s inherent to the JVM bytecode, the generic
version uses a trix that makes heavy use of reflexivity.
Because the erased
version does not use that trix, handling generic types in Kodein are a lot more complex to use.
Type erasure | Optimized | Non-generic bindings | Generic bindings | |
---|---|---|---|---|
generic |
immune |
no |
simple |
simple |
erased |
subject |
yes |
simple |
complex |
Yes, #perfmatters. However, the humble opinion of the author is that:
Therefore, please make sure that, using the erased version is right for your use case, before blindly using it ;). Do profile your code! |
On the JVM, you might prefer the erased version if:
-
You are confident you are not binding / injecting / retrieving generic types and you are sure none of the libraries you are using are.
-
You are not using set bindings.
If you profile your code and find that injection is a performance pitfall, then it probably is instanciation: you are creating too many objects in critical paths. Reusing objects in critical paths will enhance performance both in dependency injection / retrieval and in GC!
If you are using the erased version, either by choice on the JVM, or by default on JS & Native, you should read erased version pitfalls.
Install
JVM
With Maven
Add the JCenter repository:
<repositories>
<repository>
<id>jcenter</id>
<url>https://jcenter.bintray.com</url>
</repository>
</repositories>
Then add the dependency:
<dependencies>
<dependency>
<groupId>org.kodein.di</groupId>
<artifactId>kodein-di-generic-jvm</artifactId>
<version>6.5.5</version>
</dependency>
</dependencies>
Use kodein-di-generic-jvm or kodein-di-erased-jvm .
|
JavaScript (Gradle)
Because Kodein for JavaScript is compiled as a UMD module, it can be imported:
-
In a browser:
-
as an AMD module (for example with RequireJS) (See index.html in the demo project).
-
Directly in an HTML page with a
<script>
tag (See index2.html in the demo project).
-
-
In NodeJS, as a regular CJS module.
Add the JCenter repository:
buildscript {
repositories {
jcenter()
}
}
Then add the dependency:
dependencies {
compile 'org.kodein.di:kodein-di-erased-js:6.5.5'
}
Native (Gradle)
Kodein supports the following targets: androidArm32, androidArm64, iosArm32, iosArm64, iosX64, linuxArm32Hfp, linuxMips32, linuxMipsel32, linuxX64, macosX64, mingwX64 |
Kodein-DI uses the new gradle native dependency model. Because that model is experimental in gradle, it is not forward compatible with next versions of Gradle.
Add the JCenter repository:
buildscript {
repositories {
jcenter()
}
}
Then add the dependency:
kotlin {
sourceSets {
commonMain {
dependencies {
implementation "org.kodein.di:kodein-di-erased:6.5.5"
}
}
}
}
Bindings: Declaring dependencies
val kodein = Kodein {
/* Bindings */
}
Bindings are declared inside a Kodein initialization block.
If you are using kodein-di-generic-jvm , Kodein not subject to type erasure (e.g. You can bind both a List<Int> and a List<String> ).
|
This is NOT the case when using kodein-di-erased-jvm , kodein-erased-js or kodein-erased-native .
With the erased version by default, binding List<Int> and List<String> actually means binding List<*> twice.
|
A binding always starts with bind<TYPE>() with
.
There are different ways to declare bindings:
Tagged bindings
All bindings can be tagged to allow you to bind different instances of the same type.
val kodein = Kodein {
bind<Dice>() with ... (1)
bind<Dice>(tag = "DnD10") with ... (2)
bind<Dice>(tag = "DnD20") with ... (2)
}
1 | Default binding (with no tag) |
2 | Bindings with tags ("DnD10" and "DnD20" ) |
The tag is of type Any , it does not have to be a String .
|
Whether at define, at injection or at retrieval, tag should always be passed as a named argument.
|
Tag objects must support equality & hashcode comparison. It is therefore recommended to either use primitives (Strings, Ints, etc.) or data classes. |
Provider binding
This binds a type to a provider function, which is a function that takes no arguments and returns an object of the bound type (eg. () → T
).
The provided function will be called each time you need an instance of the bound type.
val kodein = Kodein {
bind<Dice>() with provider { RandomDice(6) }
}
Singleton binding
This binds a type to an instance of this type that will lazily be created at first use via a singleton function, which is a function that takes no arguments and returns an object of the bound type (eg. () → T
).
Therefore, the provided function will be called only once: the first time an instance is needed.
val kodein = Kodein {
bind<DataSource>() with singleton { SqliteDS.open("path/to/file") }
}
Non-synced singleton
By definition, there can be only one instance of a singleton, which means only one instance can be constructed. To achieve this certainty, Kodein synchronizes construction. This means that, when a singleton instance is requested and not available, Kodein uses a synchronization mutex to ensure that other request to the same type will wait for this instance to be constructed.
While this behaviour is the only way to ensure the singleton’s correctness, it is also costly (due to the mutex) and degrades startup performance.
If you need to improve startup performance, if you know what you are doing, you can disable this synchronization.
val kodein = Kodein {
bind<DataSource>() with singleton(sync = false) { SqliteDS.open("path/to/file") }
}
Using sync = false
means that:
-
There will be no construction synchronicity.
-
There may be multiple instance constructed.
-
Instance will be reused as much as possible.
Eager singleton
This is the same as a regular singleton, except that the provided function will be called as soon as the Kodein instance is created and all bindings are defined.
val kodein = Kodein {
// The SQLite connection will be opened as soon as the kodein instance is ready
bind<DataSource>() with eagerSingleton { SqliteDS.open("path/to/file") }
}
Factory binding
This binds a type to a factory function, which is a function that takes an argument of a defined type and that returns an object of the bound type (eg. (A) → T
).
The provided function will be called each time you need an instance of the bound type.
val kodein = Kodein {
bind<Dice>() with factory { sides: Int -> RandomDice(sides) }
}
Multi-arguments factories
This multi-agrument-factories mechanism is deprecated and will be removed in version 7.0
|
A factory can take multiple (up to 5) arguments:
val kodein = Kodein {
bind<Dice>() with factory { startNumber: Int, sides: Int -> RandomDice(sides) }
}
We recommand to use data classes instead!
|
Regarding our users feedbacks, we find out that multi-arguments factories was difficult to use.
Thus this mechanism will be deprecate soon. So we highly recommend that you migrate your multi-args factories to simple factories by using data classes.
data class DiceParams(val startNumber: Int, val sides: Int)
val kodein = Kodein {
bind<Dice>() with factory { params: DiceParams -> RandomDice(params) }
}
Multiton binding
A multiton can be thought of a "singleton factory": it guarantees to always return the same object given the same argument. In other words, for a given argument, the first time a multiton is called with this argument, it will call the function to create an instance; and will always yield that same instance when called with the same argument.
val kodein = Kodein {
bind<RandomGenerator>() with multiton { max: Int -> SecureRandomGenerator(max) }
}
Just like a factory, a multiton can take multiple (up to 5) arguments.
Referenced singleton or multiton binding
A referenced singleton is an object that is guaranteed to be single as long as a reference object can return it. A referenced multiton is an object that is guaranteed to be single for the same argument as long as a reference object can return it.
A referenced singleton or multiton needs a "reference maker" in addition to the classic construction function that determines the type of reference that will be used.
Kodein comes with three reference makers for the JVM:
JVM: Soft & weak
These are objects that are guaranteed to be single in the JVM at a given time, but not guaranteed to be single during the application lifetime. If there are no more strong references to the instances, they may be GC’d and later, re-created.
Therefore, the provided function may or may not be called multiple times during the application lifetime.
val kodein = Kodein {
bind<Map>() with singleton(ref = softReference) { WorldMap() } (1)
bind<Client>() with singleton(ref = weakReference) { id -> clientFromDB(id) } (2)
}
1 | Because it’s bound by a soft reference, the JVM will GC it before any OutOfMemoryException can occur. |
2 | Because it’s bound by a weak reference, the JVM will GC it is no more referenced. |
Weak singletons use JVM’s WeakReference
while soft singletons use JVM’s SoftReference
.
JVM: Thread local
This is the same as the standard singleton binding, except that each thread gets a different instance. Therefore, the provided function will be called once per thread that needs the instance, the first time it is requested.
val kodein = Kodein {
bind<Cache>() with singleton(ref = threadLocal) { LRUCache(16 * 1024) }
}
Semantically, thread local singletons should use [scoped-singletons], the reason it uses a referenced singleton is because Java’s ThreadLocal acts like a reference.
|
Thread locals are not available in JavaScript. |
Instance binding
This binds a type to an instance that already exist.
val kodein = Kodein {
bind<DataSource>() with instance(SqliteDataSource.open("path/to/file")) (1)
}
1 | Instance is used with parenthesis: it is not given a function, but an instance. |
Constant binding
It is often useful to bind "configuration" constants.
Constants are always tagged. |
val kodein = Kodein {
constant(tag = "maxThread") with 8 (1)
constant(tag = "serverURL") with "https://my.server.url" (1)
}
1 | Note the absence of curly braces: it is not given a function, but an instance. |
You should only use constant bindings for very simple types without inheritance or interface (e.g. primitive types and data classes). |
Direct binding
Sometimes, it may seem overkill to specify the type to bind
if you are binding the same type as you are creating.
For this use case, you can transform any bind<Type>() with …
to bind() from …
.
val kodein = Kodein {
bind() from singleton { RandomDice(6) }
bind("DnD20") from provider { RandomDice(20) }
bind() from instance(SqliteDataSource.open("path/to/file"))
}
This should be used with care as binding a concrete class and, therefore, having concrete dependencies is an anti-pattern that later prevents modularisation and mocking / testing. |
When using kodein-generic-* and binding a generic type, the bound type will be the specialized type,e.g. bind() from singleton { listOf(1, 2, 3, 4) } registers the binding to List<Int> .
|
If you are using Kodein/Native, because of this bug, you need to use the uppercase version: Bind() from .
This issue has been fixed and the bind() from syntax will be available to Kodein/Native as soon as Kotlin/Native 0.6 is released.
|
Subtypes bindings
Kodein allows you register a "subtype bindings factory". These are big words for a simple concept that’s best explained with an example:
val kodein = Kodein {
bind<Controller>().subtypes() with { type ->
when (type.jvmType) { (1)
MySpecialController::class.java -> singleton { MySpecialController() }
else -> provider { myControllerSystem.getController(type.jvmType) }
}
}
}
1 | As type is a TypeToken<*> , you can use .jvmType to get the JVM type (e.g. Class or ParameterizedType ). |
In essence, bind<Whatever>().subtypes() with { type → binding }
allows you to register, in Kodein, a binding factory that will be called for subtypes of the provided type.
Transitive dependencies
With those lazily instantiated dependencies, a dependency (very) often needs another dependency. Such classes can have their dependencies passed to their constructor. Thanks to Kotlin’s killer type inference engine, Kodein makes retrieval of transitive dependencies really easy.
class Dice(private val random: Random, private val sides: Int) {
/*...*/
}
It is really easy to bind this RandomDice
with its transitive dependencies, by simply using instance()
or instance(tag)
.
val kodein = Kodein {
bind<Dice>() with singleton { Dice(instance(), instance(tag = "max")) } (1)
bind<Random>() with provider { SecureRandom() } (2)
constant(tag "max") with 5 (2)
}
1 | Binding of Dice . It gets its transitive dependencies by using instance() and instance(tag) . |
2 | Bindings of Dice transitive dependencies. |
The order in which the bindings are declared has no importance whatsoever. |
The binding functions are in the same environment as the newInstance
function described in the dependency injection section.
You can read it to learn more about the instance
, provider
and factory
functions available to the function.
Transitive factory dependencies
Maybe you need a dependency to use one of its functions to create the bound type.
val kodein = Kodein {
bind<DataSource>() with singleton { MySQLDataSource() }
bind<Connection>() with provider { instance<DataSource>().openConnection() } (1)
}
1 | Using a DataSource as a transitive factory dependency. |
Being responsible for its own retrieval
If the bound class is KodeinAware, you can pass the kodein
object to the class so it can itself use the Kodein container to retrieve its own dependencies.
val kodein = Kodein {
bind<Manager>() with singleton { ManagerImpl(kodein) } (1)
}
1 | ManagerImpl is given a Kodein instance. |
Bindings separation
Modules
Definition
Kodein allows you to export your bindings in modules. It is very useful to have separate modules defining their own bindings instead of having only one central binding definition. A module is an object that you can construct the exact same way as you construct a Kodein instance.
val apiModule = Kodein.Module(name = "API") {
bind<API>() with singleton { APIImpl() }
/* other bindings */
}
Then, in your Kodein binding block:
val kodein = Kodein {
import(apiModule)
/* other bindings */
}
Modules are definitions, they will re-declare their bindings in each Kodein instance you use. If you create a module that defines a singleton and import that module into two different Kodein instances, then the singleton object will exist twice: once in each Kodein instance. |
Name uniqueness
Each module name should only be imported once.
If a second module with the name of an already imported module is imported, then Kodein will fail.
However, you cannot always ensure that every module name is unique: you may need to import modules that are defined outside of your code. Kodein offers two ways to mitigate that:
-
Rename a module:
Use when you are importing a module whose name already exists.Example: imports a renamed moduleval kodein = Kodein { import(apiModule.copy(name = "otherAPI")) }
-
Add a prefix to modules imported by a module:
Use when a module imported by another module uses a names which already exists.Example: imports a module with a prefix for sub-modulesval kodein = Kodein { import(apiModule.copy(prefix = "otherAPI-")) }
Import once
You may define a module which you know depends on another module, so it would be great to import that dependency inside the module that has the dependency. However, each module can only be imported once, so if every module that depends on another module imports it, Kodein will fail at the second module that imports it.
To support this, Kodein offers importOnce
: it imports the module if no module with that name was previously imported.
val appModule = Kodein.Module {
importOnce(apiModule)
}
Extension (composition)
Kodein allows you to create a new Kodein instance by extending an existing one.
val subKodein = Kodein {
extend(appKodein)
/* other bindings */
}
This preserves bindings, meaning that a singleton in the parent Kodein will continue to exist only once. Both parent and child Kodein objects will give the same instance. |
Overriding
By default, overriding a binding is not allowed in Kodein. That is because accidentally binding twice the same (class,tag) to different instances/providers/factories can cause real headaches to debug.
However, when intended, it can be really interesting to override a binding, especially when creating a testing environment. You can override an existing binding by specifying explicitly that it is an override.
val kodein = Kodein {
bind<API>() with singleton { APIImpl() }
/* ... */
bind<API>(overrides = true) with singleton { OtherAPIImpl() }
}
By default, modules are not allowed to override, even explicitly. You can allow a module to override some of your bindings when you import it (the same goes for extension):
val kodein = Kodein {
/* ... */
import(testEnvModule, allowOverride = true)
}
The bindings in the module still need to specify explicitly the overrides. |
Sometimes, you just want to define bindings without knowing if you are actually overriding a previous binding or defining a new. Those cases should be rare and you should know what you are doing.
val testModule = Kodein.Module(name = "test", allowSilentOverride = true) {
bind<EmailClient>() with singleton { MockEmailClient() } (1)
}
1 | Maybe adding a new binding, maybe overriding an existing one, who knows? |
If you want to access an instance retrieved by the overridden binding, you can use overriddenInstance. This is useful if you want to "enhance" a binding (for example, using the decorator pattern).
val testModule = Kodein.Module(name = "test") {
bind<Logger>(overrides = true) with singleton { FileLoggerWrapper("path/to/file", overriddenInstance()) } (1)
}
1 | overriddenInstance() will return the Logger instance retrieved by the overridden binding. |
Overridden access from parent
Let’s consider the following code :
val parent = Kodein {
bind<Foo>() with provider { Foo1() }
bind<Bar>() with singleton { Bar(foo = instance<Foo>()) }
}
val child = Kodein {
extend(parent)
bind<Foo>(overrides = true) with provider { Foo2() }
}
val foo = child.instance<Bar>().foo
In this example, the foo
variable will be of type Foo1
.
Because the Bar
binding is a singleton
and is declared in the parent
Kodein, it does not have access to bindings declared in child
.
In this example, both parent.instance<Bar>().foo
and child.instance<Bar>().foo
will yield a Foo1
object.
This is because Bar is bound to a singleton , the first access would define the container used (parent or child ).
If the singleton were initialized by child , then a subsequent access from parent would yeild a Bar with a reference to a Foo2 , which is not supposed to exist in parent .
|
By default, all bindings that do not cache instances (basically all bindings but singleton and multiton ) are copied by default into the new container, and therefore have access to the bindings & overrides of this new container.
|
If you want the Bar
singleton to have access to the overridden Foo
binding, you need to copy it into the child
container.
val child = Kodein {
extend(parent, copy = Copy {
copy the binding<Bar>() (1)
})
bind<Foo>(overrides = true) with provider { Foo2() }
}
Copying a binding means that it will exists once more. Therefore, a copied singleton will no longer be unique and have TWO instances, one managed by each binding (the original and the copied). |
If the binding you need to copy is bound by a context (such as a scoped singleton), you need to specify it:
val parent = Kodein {
bind<Session>(tag = "req") with scoped(requestScope).singleton { context.session() }
}
val child = Kodein {
extend(parent, copy = Copy {
copy the binding<Session>() with scope(requestScope) and tag("req")
})
bind<Foo>(overrides = true) with provider { Foo2() }
}
You can use the context<>() , scope() and tag() functions to specialise your binding copies.
|
You can also copy all bindings that matches a particular definition :
val child = Kodein {
extend(parent, copy = Copy {
copy all binding<String>() (1)
copy all scope(requestScope) (2)
})
}
1 | Will copy all bindings for a String , with or without a context, scope, tag or argument. |
2 | Will copy all bindings that are scoped inside a RequestScope . |
Finally, you can simply copy all bindings:
val child = Kodein {
extend(parent, copy = Copy.All)
}
Or you can decide that none are copied (if you do want existing bindings to have access to new bindings):
val child = Kodein {
extend(parent, copy = Copy.None)
}
Dependency injection & retrieval
val kodein = Kodein {
bind<Dice>() with factory { sides: Int -> RandomDice(sides) }
bind<DataSource>() with singleton { SqliteDS.open("path/to/file") }
bind<Random>() with provider { SecureRandom() }
bind<FileAccess>() with factory { path: String, mode: Int -> FileAccess.open(path, mode) }
constant("answer") with "fourty-two"
}
Retrieval rules
-
A dependency bound with a
provider
, aninstance
, asingleton
, aneagerSingleton
, or aconstant
can be retrieved:-
as a provider method:
() → T
-
as an instance:
T
-
-
A dependency bound with a
factory
or amultiton
can only be retrieved as a factory method:(A) → T
.-
as a factory method:
(A) → T
-
as a provider method:
() → T
if the argumentA
is provided at retrieval. -
as an instance:
T
if the argumentA
is provided at retrieval.
-
Injection & Retrieval
When dependencies are injected, the class is provided its dependencies at construction.
When dependencies are retrieved, the class is responsible for getting its own dependencies.
Using dependency injection is a bit more cumbersome, but your classes are "pure": they are unaware of the dependency container. Using dependency retrieval is easier (and allows more tooling), but it does binds your classes to the Kodein API.
Finally, in retrieval, everything is lazy by default, while there can be no lazy-loading using injection.
If you are developing a library, then you probably should use dependency injection, to avoid forcing the users of your library to use Kodein as well. If you are developing an application, then you should consider using dependency retrieval, as it is easier to use and provides more tooling. |
Base methods
Whether you are using dependency injection or retrieval, the same 3 methods will be available with the same name and parameters (but not return type).
These methods are:
-
instance()
if you need an instance:T
. -
provider()
if you need a provider:() → T
. -
factory()
if you need an instance:(A) → T
.
All three methods can take a tag
argument.
The Example: Using the named tag argument.
|
Injection
To use dependency injection,
-
Declare your dependencies in the constructor of your classes.
-
Use Kodein’s
newInstance
method to create an object of such class.
Simple case
class MainController(val ds: DataSource, val rnd: Random) { /*...*/ }
val controller by kodein.newInstance { MainController(instance(), instance(tag = "whatever")) } (1)
1 | Note the use of the instance function that will inject the correct dependency. |
When injecting a type that was not bound, a Kodein.NotFoundException will be thrown.
|
If you are not sure (or simply do not know) if the type has been bound, you can use *OrNull
methods.
Multi-arguments factories
When injecting a value that was bound with a multi-argument factory, the arguments must be wrapped inside a data class:
data class ControllerParams(val path: String, val timeout: Int)
val controller by kodein.newInstance { FileController(instance(args = ControllerParams("path/to/file", 0))) }
Currying factories
You can retrieve a provider or an instance from a factory bound type by using the arg
parameter (this is called currying).
class RollController(val dice: Dice) { /*...*/ }
val controller by kodein.newInstance { RollController(instance(arg = 6)) }
Note that if you want to bind a factory with multiple argument, you need to use a data class to pass multiple arguments:
data class Params(val arg1: Int, val arg2: Int)
val controller by kodein.newInstance { RollController(instance(arg = Params(60, 6))) }
The arg argument should always be named.
|
Defining context
When retrieving, you sometimes need to manually define a context (for example, when retrieving a scoped singleton).
For this, you can use the on
method:
val controller by kodein.on(context = myContext).newInstance { OtherController(instance(arg = 6), instance()) }
The context argument should always be named.
|
Sometimes, the context is not available directly at construction. When that happens, you can define a lazy context that will be accessed only when needed.
val controller by kodein.on { requireActivity() } .newInstance { OtherController(instance(arg = 6), instance()) }
Retrieval: the Kodein container
everything is lazy by default!
In the next few sections, we will be describing dependency retrieval. As you might have guessed by the title of this section, everything, in dependency retrieval, is lazy by default.
This allows:
-
Dependencies to be retrieved only when they are actually needed.
-
"Out of context" classes such as Android Activities to access their dependencies once their contexts have been initialized.
If you want "direct" retrieval, well, there’s a section named direct retrieval, how about that!
Kodein methods
You can retrieve a bound type via a Kodein instance.
val diceFactory: (Int) -> Dice by kodein.factory()
val dataSource: DataSource by kodein.instance()
val randomProvider: () -> Random by kodein.provider()
val answerConstant: String by kodein.instance(tag = "answer")
Note the use of the by
.
Kodein uses delegated properties to enable:
-
Lazy loading
-
Accessing the receiver
When using a provider function (() → T ), whether this function will give each time a new instance or the same depends on the binding.
|
When asking for a type that was not bound, a Kodein.NotFoundException will be thrown.
|
If you are not sure (or simply do not know) if the type has been bound, you can use *OrNull
methods.
val diceFactory: ((Int) -> Dice)? by kodein.factoryOrNull()
val dataSource: DataSource? by kodein.instanceOrNull()
val randomProvider: (() -> Random)? by kodein.providerOrNull()
val answerConstant: String? by kodein.instanceOrNull(tag = "answer")
Constants
If you bound constants, you can easily retrieve them with the constant method if the name of the property matches the tag:
val answer: String by kodein.constant()
Named bindings
If you used tagged bindings, if the tag is a String
and the property name matches the tag, instead of passing it as argument, you can use named
:
val answer: String by kodein.named.instance()
Multi-arguments factories
When retrieving a value that was bound with a multi-argument factory, the arguments must be wrapped inside a data class:
data class FileParams(val path: String, val maxSize: Int)
val fileAccess: FileAccess by kodein.instance(args = FileParams("/path/to/file", 0))
Currying factories
You can retrieve a provider or an instance from a factory bound type by using the arg
parameter (this is called currying).
val sixSideDiceProvider: () -> Dice by kodein.provider(arg = 6)
val twentySideDice: Dice by kodein.instance(arg = 20)
Note that if you bound a factory with multiple arguments, you need to use a data class to pass multiple arguments:
data class DiceParams(val startNumber: Int, val sides: Int)
val sixtyToSixtySixDice: Dice by kodein.instance(arg = DiceParams(60, 6)) (1)
1 | Bonus points if you can say the variable name 5 times in less than 5 seconds ;) |
The arg argument should always be named.
|
Defining context
Whether you are using a scoped singleton/multiton or using a context in the target binding, you may need to specify a context.
val session: Session by kodein.on(context = request).instance()
If you retrieve multiple dependencies all using the same context, you can create a new Kodein
object with the context set:
val reqKodein = kodein.on(context = request)
val session: Session by reqKodein.instance()
The context argument should always be named.
|
Using a global context does not forces you to use only bindings that are declared with this type of context.
Because the default context is Any? , all non-contexted bindings will still be available with a global context set.
|
Using a Trigger
There is a mechanism that allows you to decide when dependencies are actually retrieved if you want them to be retrieved at a particular time and not at first access. This mechanism is called a Trigger.
val trigger = KodeinTrigger()
val dice: Dice by kodein.on(trigger = trigger).instance()
/*...*/
trigger.trigger() (1)
1 | Retrieval happens now. |
You can, of course, assign multiple properties to the same trigger. You can also create a Kodein object that has a given trigger by default:
val trigger = KodeinTrigger()
val injectKodein = kodein.on(trigger = trigger)
val dice: Dice by injectKodein.instance()
/*...*/
trigger.trigger()
The trigger argument should always be named.
|
A trigger allows you to "force" retrieval.
However, retrieval can still happen before inject() is called if the variable is accessed.
|
Lazy access
Kodein proposes a LazyKodein
object that allows you to lazily access the Kodein object only when needed.
This is useful if:
-
You need to defined a lazily retrieved dependency before having access to a Kodein container.
-
You don’t know if you’ll ever need to access a Kodein object.
For this, you can use a LazyKodein
:
val kodein = LazyKodein { /* access to a kodein instance */ }
val ds: DataSource by kodein.instance()
/*...*/
dice.roll() (1)
1 | Only then will the Kodein instance will itself be retrieved. |
Note that you can also lazily create a Kodein
object so that the bindings definition function will only be called when the first retrieved property is needed:
val kodein by Kodein.lazy {
bind<Env>() with instance(Env.getInstance())
}
val env: Env by kodein.instance()
/*...*/
env.doSomething() (1)
1 | Only then will the Kodein instance will itself be created, and the bindings definition function ran. |
Late init
Kodein proposes a LateInitKodein
that allows you to define a Kodein object after some lazy retrieval:
val kodein = LateInitKodein()
val env: Env by kodein.instance()
/*...*/
kodein.baseKodein = /* access to a kodein instance */ (1)
/*...*/
env.doSomething() (2)
1 | Setting the real Kodein object. |
2 | If this was run before setting kodein.baseKodein , an UninitializedPropertyAccessException would be thrown. |
All matches
Kodein allows you to retrieve all instances that matches a given type:
val instances: List<Foo> by kodein.allInstances() (1)
1 | Will return all instances that are for bindings of sub-classes of Foo |
Of course, allProviders and allFactories are also provided ;)
|
Retrieval: being KodeinAware
Simple retrieval
You can have classes that implement the interface KodeinAware
.
Doing so has the benefit of getting a simpler syntax for retrieval.
class MyManager(override val kodein: Kodein) : KodeinAware {
private val diceFactory: ((Int) -> Dice)? by factoryOrNull()
private val dataSource: DataSource? by instanceOrNull()
private val randomProvider: (() -> Random)? by providerOrNull()
private val answerConstant: String? by instanceOrNull(tag = "answer")
private val sixSideDiceProvider: () -> Dice by kodein.provider(arg = 6)
private val twentySideDice: Dice by kodein.instance(arg = 20)
}
All methods that are available to the Kodein container are available to a KodeinAware
class.
Class global context
In a KodeinAware
class, to define a context that’s valid for the entire class, you can simply override the kodeinContext
property:
class MyManager(override val kodein: Kodein) : KodeinAware {
override val kodeinContext = kcontext(whatever) (1)
/*...*/
}
1 | Note the use of the kcontext function that creates a KodeinContext with the given value. |
Using a global context does not forces you to use only bindings that are declared with this type of context.
Because the default context is Any? , all non-contexted bindings will still be available with a global context set.
|
Sometimes, the context is not available directly at construction. When that happens, you can define a lazy context that will be accessed only when needed.
class MyManager(override val kodein: Kodein) : KodeinAware {
override val kodeinContext = kcontext { requireActivity }
/*...*/
}
Class global trigger
If you want to have all dependency properties retrieved at once, you can use a class global trigger.
Simply override the kodeinTrigger
property:
class MyManager(override val kodein: Kodein) : KodeinAware {
override val kodeinTrigger = KodeinTrigger()
val ds: DataSource by instance()
/*...*/
fun onReady() {
kodeinTrigger.trigger() (1)
}
}
1 | Retrieval of all dependencies happens now. |
Lazy access
Some classes (such as Android Activities) do not have access to a Kodein
instance at the time of construction, but only later when they have been properly connected to their environment (Android context).
Because Kodein is lazy by default, this does not cause any issue: simply have the kodein property be lazy by itself:
class MyActivity : Activity(), KodeinAware {
override val kodein by lazy { (applicationContext as MyApplication).kodein }
val ds: DataSource by instance() (1)
}
1 | Because ds is lazily retrieved, access to the kodein property will only happen at first retrieval. |
There is an official module to ease the use of Kodein in Android, you can read more about it on the dedicated document. |
Lateinit
Because everything is lazy and, in a KodeinAware class, the Kodein object is not accessed until needed, you can easily declare the kodein
field as lateinit.
class MyActivity : Activity(), KodeinAware {
override val lateinit kodein: Kodein
val ds: DataSource by instance() (1)
override fun onCreate(savedInstanceState: Bundle?) {
kodein = (applicationContext as MyApplication).kodein
}
}
1 | Because ds is lazily retrieved, access to the kodein property will only happen at first retrieval. |
All matches
Kodein allows you to retrieve all instances that matches a given type:
val instances: List<Foo> = dkodein.allInstances() (1)
1 | Will return all instances that are for bindings of sub-classes of Foo |
Of course, allProviders and allFactories are also provided ;)
|
Retrieval: Direct
If you don’t want to use delegated properties, Kodein has you covered.
Most of the features available to Kodein
are available to DKodein
(D is for Direct).
DKodein
allows you to directly get a new instance or dependency.
However, because it is direct, DKodein
does NOT feature:
-
Laziness: the instance/provider/factory is fetched at call time.
-
Receiver awareness: receiver is defined by the Kotlin’s delegated properties mechanism.
val dk = kodein.direct
val ds: Datasource = dk.instance()
val controller = dk.newInstance { MainController(instance(), instance(tag = "whatever")) }
If you only plan to use direct access, you can define your main kodein object to be a Example: using a DKodein
<1>: Note the |
Being DKodeinAware
Much like Kodein
offers KodeinAware
, DKodein
offers DKodeinAware
class MyManager(override val dkodein: DKodein) : DKodeinAware {
private val diceFactory: ((Int) -> Dice)? = factoryOrNull()
private val dataSource: DataSource? = instanceOrNull()
private val randomProvider: (() -> Random)? = providerOrNull()
private val answerConstant: String? = instanceOrNull(tag = "answer")
private val sixSideDiceProvider: () -> Dice = kodein.provider(arg = 6)
private val twentySideDice: Dice = kodein.instance(arg = 20)
}
In Java
While Kodein does not allow you to declare modules or dependencies in Java, it does allow you to retrieve dependencies via DKodein
.
Simply give the DKodein instance to your Java classes, use Kodein in Java with the TT
static function:
import static org.kodein.di.TypesKt.TT;
public class JavaClass {
private final Function1<Integer, Dice> diceFactory;
private final Datasource dataSource;
private final Function0<Random> randomProvider;
private final String answerConstant;
public JavaClass(DKodein kodein) {
diceFactory = kodein.Factory(TT(Integer.class), TT(Dice.class), null);
dataSource = kodein.Instance(TT(Datasource.class), null);
randomProvider = kodein.Provider(TT(Random.class), null);
answerConstant = kodein.Instance(TT(String.class), "answer");
}}
Remember that Java is subject to type erasure.
Therefore, if you registered a generic Class binding such as Example: using TypeReference in Java
|
Error messages
By default, Kodein error messages contains the classes simple names (e.g. View
), which makes it easily readable.
If you want the error to contain classes full names (e.g. com.company.app.UserController.View
), you can set fullDescriptionOnError
:
val kodein = Kodein {
fullDescriptionOnError = true
}
If you are using multiple Kodein instances, you can set the default value fullDescriptionOnError
for all subsequently created Kodein instances:
Kodein.defaultFullDescriptionOnError = true
Kodein.defaultFullDescriptionOnError must be set before creating a Kodein instance.
|
Using the environment
Binding functions have access to the environment where the bound type is retrieved to be able to create it accordingly.
Context
This environment is represented as a context variable.
The context is an object that is explicitly defined by the programmer for this retrieval or the receiving object when none is explicitely defined.
There are two very important differences between a tag and a context:
-
The tag instance identifies the binding but can not be used in the binding function.
-
The context type identifies the binding and it’s instance can be used in the binding function.
There are also two very important differences between a factory argument and a context:
-
The context is defined before retrieving the binding function while the factory argument is the last known variable.
-
A context is usually global to an entire class while a factory argument is local to a retrieval.
When in doubt, use a factory with an argument instead of a provider with a context. |
val kodein = Kodein {
bind<Writer>() with contexted<Request>.provider { context.response.writer } (1)
}
1 | note that context is already of type Request . |
Scope
Kodein provides only 1 scope by default, but:
|
Scopes are derived from a context variable.
They allow a singleton or multiton objects to exist multiple times in different contexts.
They are of type Scope<C>
where C
is the context type.
Think, for example, of a session object inside a web server.
We can say that there can be only one user per session, and therefore define a User
singleton scoped in a session.
Therefore, the provided function will be called once per session.
val kodein = Kodein {
bind<User>() with scoped(SessionScope).singleton { UserData(session.userId) } (1)
}
1 | note that SessionScope does not really exist, it is an example. |
In this example, SessionScope
is of type Scope<Session>
, so to access this binding, the user will either have retrieve it inside the session object or explicitly define a Session
context:
val user by kodein.on(session).instance()
Please read the Scope creation section if you want to create your own scopes. |
Scope closeable
By default, a Singleton or a Multiton value will never expire.
However, the purpose of a Scope is to handle the lifecycle of a long lived value.
Therefore, it is possible for a scoped Singleton or Multiton value to expire (most of the time because the scope itself expires).
For example, in android’s ActivityRetainedScope
, scoped values will only live the duration of the activity.
If a value implements ScopeCloseable
, it’s close
function will be called when the value is removed from the scope (or when the scope itself expires).
The
|
JVM references in scopes
Yes, you can…
val kodein = Kodein {
bind<User>() with scoped(requestScope).singleton(ref = weakReference) {
instance<DataSource>().createUser(context.session.id)
} (1)
}
Weak Context Scope
Kodein provides the WeakContextScope
scope.
This is a particular scope, as the context it holds on are weak references.
WeakContextScope is NOT compatible with ScopeCloseable .
|
You can use this scope when it makes sense to have a scope on a context that is held by the system for the duration of its life cycle.
val kodein = Kodein {
bind<Controller>() with scoped(WeakContextScope.of<Activity>()).singleton { ControllerImpl(context) } (1)
}
1 | context is of type Activity because we are using the WeakContextScope.of<Activity>() . |
WeakContextScope.of
will always return the same scope, which you should never clean!
If you need a compartimentalized scope which you can clean, you can create a new WeakContextScope
:
val activityScope = WeakContextScope<Activity>()
Context translators
Let’s get back to the web server example.
There is one session per user, so we have bound a User
singleton inside a Session
scope.
As each Request
is associated with a Session
, you can register a context translator that will make any binding that needs a Session
context work with a Request
context:
val kodein = Kodein {
bind<User>() with scoped(SessionScope).singleton { UserData(session.userId) }
registerContextTranslator { r: Request -> r.session }
}
This allows you to retrieve a User
instance:
-
When there is a global
Request
context:Example: retriving with a global contextclass MyController(override val kodein: Kodein, request: Request): KodeinAware { override val kodeinContext = kcontext(request) val user: User by instance() }
-
When the retrieval happens on a
Request
itself:Example: retriving with a global contextclass MySpecialRequest(override val kodein: Kodein): Request(), KodeinAware { val user: User by instance() }
Context finder
A context finder is a similar to context translator, except that it gets the context from a global context.
For example, if you are in a thread-based server where each request is assigned a thread (are people still doing those?!?), you could get the session from a global:
val kodein = Kodein {
bind<User>() with scoped(SessionScope).singleton { UserData(session.userId) }
registerContextFinder { ThreadLocalSession.get() }
}
This allows to access a User
object wihout specifying a context.
Having an other type of context declared will not block from using a context finder. |
Scope creation
Scoped singletons/multitons are bound to a context and live while that context exists.
To define a scope that can contain scoped singletons or multitons, you must define an object that implements the Scope
interface.
This object will be responsible for providing a ScopeRegistry
according to a context.
It should always return the same ScopeRegistry
when given the same context object.
A standard way of doing so is to use the userData
property of the context, if it has one, or else to use a WeakHashMap<C, ScopeRegistry>
.
object SessionScope : Scope<Session> { (1)
override fun getRegistry(context: Session): ScopeRegistry =
context.userData as? ScopeRegistry
?: StandardScopeRegistry().also { context.userData = it } (2)
}
1 | The scope’s context type is Session . |
2 | Creates a ScopeRegistry and attach it to the Session if there is none. |
Scope providers should also provide standard context translators. In this example, we should provide, along with sessionScope a module providing the Request to Session context translator.
|
Scope registry
The ScopeRegistry
is responsible for holding value instances.
It is also responsible for calling the close
methods on object that are ScopeCloseable
when they are removed from the registry.
To have your scope compatible with ScopeCloseable values, make sure to clean the registry when the scope expires.
|
There are two standard implementations of ScopeRegistry
:
SingleItemScopeRegistry
This is a particular ScopeRegistry
implementation : it will only hold one item and replace the held item if the binding asks for an instance of another binding.
This means that a Multiton scoped with a Scope that uses a SingleItemScopeRegistry
will actually hold only one instance: the one corresponding to the last argument.
You should NOT use this registry unless you know exactly WHAT you are doing, and WHY you are doing it. |
Sub-scopes
You can define a scope to be defined inside another scope. This means that when the parent scope clears, so does all of its subscopes.
val requestScope = object : SubScope<Request, Session>(sessionScope) {
override fun getParentContext(context: Request) = context.session
}
In this simple example, when the session expires, then all of its associates request scoped values also expire.
Multi-binding
Kodein allows multi bindings via a binding set.
In a Set
Binding in a Set
To have multiple bindings in a set, you need to:
-
Declare that you are using a set binding for a particular bound type.
-
Add bindings to the set.
Configuration
bindings.val kodein = Kodein {
bind() from setBinding<Configuration>() (1)
bind<Configuration>().inSet() with provider { FooConfiguration() } (2)
bind<Configuration>().inSet() with singleton { BarConfiguration() } (2)
}
1 | Creating a set binding of Configuration . |
2 | Binding multiple Configuration implementations. |
You can:
|
You can also bind multiple bindings with arguments (such as factory
or multiton
) in a set as long as all bindings share the same argument type.
Result
bindings.val kodein = Kodein {
bind() from argSetBinding<Query, Result>()
bind<Result>().inSet() with factory { q: Query -> Foo.query(q) }
bind<Result>().inSet() with multiton { q: Query -> Bar.query(q) }
}
Retrieving from a Set
Note that the type being bound is Set<T>
, not T
.
Therefore, you need to retrieve a Set
:
Configuration
with the generic version.val configurations: Set<Configuration> by kodein.instance()
if you are using the erased
version, you need to retrieve thusly:
Configuration
with the erased version.val configurations: Set<Configuration> by kodein.Instance(erasedSet())
In a map
Kodein does not directly support map multi-binding. However, it is very easy to create a binding map by using a binding set.
First, create the following primitive:
Map<String, Configuration>
.typealias ConfigurationEntry = Pair<String, Configuration>
typealias ConfigurationEntries = Set<ConfigurationEntry>
Then, bind with keys:
val kodein = Kodein {
bind() from setBinding<ConfigurationEntry>()
bind<ConfigurationEntry>().inSet() with factory { "foo" to FooConfiguration() }
bind<ConfigurationEntry>().inSet() with multiton { "bar" to BarConfiguration() }
}
Finally, retrieve the map:
val configurations by kodein.instance<ConfigurationEntries>().toMap()
Debugging
Print bindings
You can easily print bindings with println(kodein.container.tree.bindings.description)
.
Here’s an example of what this prints:
bind<Dice>() with factory { Int -> RandomDice } bind<DataSource>() with singleton { SQLiteDataSource } bind<Random>() with provider { SecureRandom } bind<String>(tag = "answer") with instance ( Int )
As you can see, it’s really easy to understand which type with which tag is bound to which implementation inside which scope.
Description prints type names in a "kotlin-esque" way.
Because Kodein does not depend on kotlin-reflect , it uses java Type objects that do not contain nullability information.
As such, the type display does not include nullability. Still, it’s easier to read List<*> than List<? extends Object> .
|
Recursive dependency loop
When it detects a recursive dependency, Kodein will throw a Kodein.DependencyLoopException
.
The message of the exception explains how the loop happened.
Kodein$DependencyLoopException: Dependency recursion: bind<Database>() ╔╩>bind<User>() (1) ║ ╚>bind<Repository>(tag = "users") (2) ║ ╚>bind<Database>() (3) ╚══════╝
1 | Database depends on User |
2 | User depends on Repository with the tag "users" |
3 | Repository with the tag "users" depends on Database , we have found the dependency loop!. |
OnReady callbacks
You can define callbacks to be called once the kodein instance is ready and all bindings are defined. This can be useful to do some "starting" jobs.
val appModule = Kodein.Module(name = "app") {
import(engineModule)
onReady {
val engine = instance<Engine>()
instance<Logger>().info("Starting engine version ${engine.version}")
engine.start()
}
}
External Source
An external source is responsible for providing an answer when Kodein cannot find one.
When Kodein cannot find a binding for the required type/argument/context, then it calls the external source.
val kodein = Kodein {
externalSource = ExternalSource { key ->
when (key.type.jvmType) { (1)
Whatever::class.java -> when (key.argType.jvmType) { (2)
Unit::class.java -> when (key.tag) { (3)
"user" -> externalFactory { existingInstance } (4)
null -> externalFactory { Whatever("default-value") } (4)
else -> null (6)
}
String::class.java -> when (key.tag) { (3)
null -> externalFactory { Whatever(it as String) } (5)
else -> null (6)
}
else -> null (6)
}
else -> null (6)
}
}
}
1 | The type that is required |
2 | The argument type (Unit if no argument) |
3 | The tag (null if no tag) |
4 | You can return an existing instance or a new one |
5 | The argument has been checked to be a String, so it can be safely casted |
6 | Return null if the external source has no answer |
The externalSource
property takes an ExternalSource
instance, which is a SAM interface that can be implemented by a lambda with the ExternalSource { }
constructor.
This ExternalSource
is called every time a new Key
is asked but not found.
The Key
itself contains information about the binding that was asked but not found.
The ExternalSource will be called only once per unknown key.
|
The ExternalSource
must return a function (which you can easily create with the externalFactory
utility function) that takes an Any?
argument and returns the instance.
This function will be called every time an instance is requested.
Note that if no argument is provided, the argument to the lambda will be Unit
.
Erased version pitfalls
The type erasure problem
When using the generic
version on the JVM, Kodein is immune to type erasure, meaning that bind<List<String>>()
and bind<List<Int>>()
will represent two different bindings.
Similarly, kodein.instance<List<String>>()
and kodein.instance<List<Int>>()
will yield two different list.
To be erasure immune, the generic
JVM version relies heavily on the generic
function, which is known to be slow.
To improve performance, you can use the erased
JVM Kodein version, which is faster, but do suffer from type erasure!
Furthermore, on Javascript and Native platforms, there’s no choice: erased
is the only version available!
Using generic and erased function forms
Each kodein function that handles a type exists in two form: as inline (lowercased first letter) and as regular function (uppercased first letter).
For example, the kodein.instance
function also exists as kodein.Instance
.
The uppercase functions need TypeToken
parameters that define the type being bound / retrieved and maybe the factory’s argument.
You can easily use these functions with the generic
or erased
functions:
erased
functionval ds: DataSource by kodein.Instance(erased())
By default, all inline functions are aliases to their uppercase counterparts using the generic
function.
For example, the kodein.instance()
function is an alias to kodein.Instance(generic())
So, when you know that you inject a type that is not generic, you can use kodein.Instance(erased())
.
Erased parameterized generic types
When using the erased
function or using erased by default (either by choice on the JVM or by necessity elsewhere), you cannot represent a generic type.
For example, erased<Set<String>>
will yield a TypeToken
representing Set<*>
.
Kodein provides a way to represent a generic type in an erased way:
erasedComp1<Set<String>, String>() // Represents a Set<String>
erasedComp2<Map<Int, String>, Int, String>() // Represents a Map<Int, String>
erasedComp3<Triple<Int, String, Int>, Int, String, Int>() // Represents a Triple<Int, String, Int>
The type parameter themselves are erased, meaning that you cannot represent a multi-level generic type.
You can, however, construct your own CompositeTypeToken to represent such a type.
|
Bind the same type to different factories
Yeah, when I said earlier that "you can have multiple bindings of the same type, as long as they are bound with different tags", I lied.
Because each binding is actually a factory, the binding tuples are not ([BindType], [Tag])
but actually ([ContextType], [BindType], [ArgType], [Tag])
(note that providers and singletons are bound as ([BindType], Unit, [Tag])
).
This means that any combination of these three information can be bound to it’s own factory, which in turns means that you can bind the same type without tagging to different factories.
Please be cautious when using this knowledge, as other less thorough readers may get confused with it. |
Hack the container!
The KodeinContainer is the sacred Kodein object that contains all bindings and is responsible for retrieval.
You can access it with kodein.container
.
In it, each Binding
is bound to a Kodein.Key
.
In fact, all Kodein functions are proxies to this container API.
When defining bindings, in the Kodein.Builder
, you can access the container
property to bind factories to a Kodein.Key
or a Kodein.Bind
.
Community
Contribute
Contributions are very welcome and greatly appreciated! The great majority of pull requests are eventually merged.
To contribute, simply fork the project on Github, fix whatever is iching you, and submit a pull request!
I am sure that this documentation contains typos, inaccuracies and languages error (English is not my mother tongue). If you feel like enhancing this document, you can propose a pull request that modifies the documentation documents. (Documentation is auto-generated from those).
Let’s talk!
You’ve read so far?! You’re awesome!
Why don’t you drop by the Kodein Slack channel on Kotlin’s Slack group?