- Introduction
- Install
- Bindings: Declaring dependencies
- Bindings separation
- Dependency retrieval
- Configurable Kodein
- Android
- Debugging
- Performance
- Using reflexivity to auto-inject
- Advanced use
- Get involved!
Kodein is a very simple and yet very useful dependency 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 or provider.
-
Easily debug your dependency bindings and recursions.
-
Automatically instantiate your dependencies via injected constructor and reflexivity. For that, you need Guice.
-
Have dependency injection validated at compile time. For that, you need Dagger.
-
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.
<dependency>
<groupId>com.github.salomonbrys.kodein</groupId>
<artifactId>kodein</artifactId>
<version>3.4.1</version>
</dependency>val kodein = Kodein {
/* Bindings */
}Bindings are declared inside a Kodein initialization block, and they are not subject to type erasure (e.g. You can bind both a List<Int> and a List<String> to different list instances, providers or factories).
There are different ways to declare bindings:
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. Each time you need an instance of the bound type, the function will be called.
val kodein = Kodein {
bind<Dice>() with factory { sides: Int -> RandomDice(sides) }
}This binds a type to a provider function, which is a function that takes no arguments and returns an object of the bound type. Each time you need an instance of the bound type, the function will be called.
val kodein = Kodein {
bind<Dice>() with provider { RandomDice(6) }
}This binds a type to an instance of this type that will lazily be created at first use. Therefore, the provided function will only be called once: the first time an instance is needed.
val kodein = Kodein {
bind<DataSource>() with singleton { SqliteDS.open("path/to/file") }
}This is the same as a regular singleton, except that the provider method 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") }
}A multiton can be thought of a "singleton factory". A multiton guarantees to always gives 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 create an object; and will always yield that same object when called with the same argument.
val kodein = Kodein {
bind<RandomGenerator>() with multiton { max: Int -> SecureRandomGenerator(max) }
}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.
Kodein comes with three reference makers.
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 objects, they may be GC’d and later, a new object re-created.
val kodein = Kodein {
bind<Cache>() with refSingleton(softReference) { LRUCache(16 * 1024) } (1)
}-
Because it’s bound by a soft reference, the JVM will GC it before any
OutOfMemoryExceptioncan occur.
Weak singletons use Java’s WeakReference while soft singletons use Java’s SoftReference.
This is the same as the standard singleton binding, except that each thread gets a different instance. Therefore, the provided function is called once per thread that needs the instance.
val kodein = Kodein {
bind<Cache>() with refSingleton(threadLocal) { LRUCache(16 * 1024) }
}|
Note
|
Semantically, thread local singletons should use scoped singletons, the reason it uses a referenced singleton is because Java’s ThreadLocal acts like a reference.
|
This binds a type to an instance already created.
val kodein = Kodein {
bind<DataSource>() with instance(SqliteDataSource.open("path/to/file")) // (1)
}-
Instance is used with parenthesis: it is not given a function, but an instance.
All bindings can be tagged to allow you to bind different instances of the same type.
val kodein = Kodein {
bind<Dice>() with provider { RandomDice(6) } // (1)
bind<Dice>("DnD10") with provider { RandomDice(10) } // (2)
bind<Dice>("DnD20") with provider { RandomDice(20) } // (2)
}-
Default binding (with no tag)
-
Bindings with tags
|
Important
|
You can have multiple bindings of the same type, as long as they are bound with different tags. You can have only one binding of a certain type with no tag. |
|
Tip
|
The tag is of type Any, it does not have to be a String.
|
It is often useful to bind "configuration" constants. Constants are always tagged.
val kodein = Kodein {
constant("maxThread") with 8 // (1)
constant("serverURL") with "https://my.server.url" // (1)
}-
Note the absence of curly braces: it is not given a function, but an instance.
|
Caution
|
You should only use constant bindings for very simple types without inheritance or interface (e.g. primitive types and data classes). |
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 scope to bind() from scope.
val kodein = Kodein {
bind() from singleton { RandomDice(6) }
bind("DnD20") from provider { RandomDice(20) }
bind() from instance(SqliteDataSource.open("path/to/file"))
}|
Caution
|
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. |
|
Warning
|
In the case of generic types, the bound type will be the specialized type, e.g. bind() from singleton { listOf(1, 2, 3, 4) } registers the binding to List<Int>.
|
With those lazily instantiated dependencies, a dependency (very) often needs another dependency. Such object 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 RandomDice with its transitive dependencies, by simply using instance() or instance(tag).
val kodein = Kodein {
bind<Dice>() with singleton { Dice(instance(), instance("max")) } // (1)
bind<Random>() with provider { SecureRandom() } // (2)
constant("max") with 5 // (2)
}-
Binding of
Dice. It gets its transitive dependencies by usinginstance()andinstance(tag). -
Bindings of
Dicetransitive dependencies.
|
Note
|
The order in which the bindings are declared has no importance whatsoever. |
|
Note
|
You can, of course, also use the functions provider(), provider(tag), factory() and factory(tag),
|
Finally, you can also pass the kodein object to the class so it can itself use the Kodein object to retrieve its own dependencies.
val kodein = Kodein {
bind<Manager>() with singleton { ManagerImpl(kodein) } // (1)
}-
ManagerImpl is given a Kodein instance.
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 {
bind<API>() with singleton { APIImpl() }
/* other bindings */
}Then, in your Kodein binding block:
val kodein = Kodein {
import(apiModule)
/* other bindings */
}|
Note
|
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. |
Kodein allows you to create a new kodein instance by extending an existing one.
val subKodein = Kodein {
extend(appKodein)
/* other bindings */
}|
Note
|
This preserves scopes, 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. |
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 { APIImpl() }
}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)
}|
Warning
|
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(allowSilentOverride = true) {
bind<EmailClient>() with singleton { MockEmailClient() } (1)
}-
Maybe adding a new binding, maybe overriding an existing, 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 {
bind<Logger>(overrides = true) with singleton { FileLoggerWrapper("path/to/file", overriddenInstance()) } (1)
}-
overriddenInstance()will return theLoggerinstance retrieved by the overridden binding.
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() }
constant("answer") with "fourty-two"
}-
A dependency bound with a
factorycan only be retrieved as a factory method:(A) → T. -
A dependency bound with a
provider, aninstance, asingletonor aconstantcan be retrieved:-
as a provider method:
() → T -
as an instance:
T
-
You can retrieve a dependency via a Kodein instance.
val diceFactory: (Int) -> Dice = kodein.factory()
val dataSource: DataSource = kodein.instance()
val randomProvider: () -> Random = kodein.provider()
val answerConstant: String = kodein.instance("answer")|
Note
|
When using a provider, whether the provider will give each time a new instance or the same depends on the binding scope. |
|
Warning
|
When asking for a type that was not bound, a Kodein.NotFoundException will be thrown.
|
If you’re not sure (or simply don’t know) if the type has been bound, you can use *OrNull methods.
val diceFactory: ((Int) -> Dice)? = kodein.factoryOrNull()
val dataSource: DataSource? = kodein.instanceOrNull()
val randomProvider: (() -> Random)? = kodein.providerOrNull()
val answerConstant: String? = kodein.instanceOrNull("answer")You can retrieve a provider or an instance from a factory bound type by using with (this is called currying).
private val sixSideDiceProvider: () -> Dice = kodein.with(6).provider()
private val twentySideDice: Dice = kodein.with(6).instance()You can easily create a new instance of an unbound class with the same syntax as if you bound it:
private val sql1 = kodein.newInstance { SQLiteConnection(instance()) } (1)
private val sql2 = kodein.newInstance { SQLiteConnection(instance("path")) } (2)
-
Passing a transitive dependency.
-
Passing a transitive dependency bound with a tag.
You can have classes that implement the interface KodeinAware.
Doing so has the benefit of having a simpler syntax for retrieval.
class MyManager(override val kodein: Kodein) : KodeinAware {
val ds: DataSource = instance()
}All methods that are available to Kodein are available to a KodeinAware class.
Lazy properties allow you to resolve the dependency upon first access.
class Controller(private val kodein: Kodein) {
private val diceFactory: (Int) -> Dice by kodein.lazy.factory()
private val dataSource: DataSource by kodein.lazy.instance()
private val randomProvider: () -> Random by kodein.lazy.provider()
private val answerConstant: String by kodein.lazy.instance("answer")
}kodein.lazy.factoryOrNull, kodein.lazy.providerOrNull and kodein.lazy.instanceOrNull are also available.
You can curry factories and retrieve a lazy property with the same lazy access.
private val sixSideDiceProvider: () -> Dice by kodein.with(6).lazy.provider()
private val twentySideDice: Dice by kodein.with(6).lazy.instance()If you don’t know yet the parameter to curry the factory with, you can pass a lambda. That way, the parameter will be fetched only when needed.
private val randomSideDiceProvider: () -> Dice
by kodein.with { random.nextInt(20) + 1 }.lazy.provider()An injector is an object that you can use to inject all dependency properties in an object.
-
Retrieve all its injected dependencies at once;
-
Declare its dependencies without a Kodein instance.
class Controller() {
private val injector = KodeinInjector() // (1)
private val diceFactory: (Int) -> Dice by injector.factory() // (2)
private val dataSource: DataSource by injector.instance() // (2)
private val randomProvider: () -> Random by injector.provider() // (2)
private val answerConstant: String by injector.instance("answer") // (2)
private val kodein by injector.kodein() // (3)
fun whenReady(kodein: Kodein) = injector.inject(kodein) // (4)
}-
Creating an injector
-
Creating lazy properties.
-
Creating a lazy Kodein that will be available after injection.
-
Injecting all properties created by the injector.
|
Warning
|
If you try to access a property created by an injector before calling injector.inject(kodein), a KodeinInjector.UninjectedException will be thrown.
|
injector.factoryOrNull, injector.providerOrNull and injector.instanceOrNull are also available.
As usual, you can curry factories by using with.
private val sixSideDiceProvider: () -> Dice by injector.with(6).provider()
private val tenSideDiceProvider: Dice by injector.with(10).instance()You can have classes that implement the interface KodeinInjected.
Doing so has the benefit of having a simpler syntax for injection.
class MyManager() : KodeinInjected {
override val injector = KodeinInjector()
val ds: DataSource by instance()
}All methods that are available to KodeinInjector are available to a KodeinInjected class.
Sometimes, you don’t directly have access to a Kodein instance.
In these cases, if you don’t want to use an injector, you can use LazyKodein.
class Controller() {
private val kodein = LazyKodein { /* code to access a Kodein instance */ } // (1)
private val diceFactory: (Int) -> Dice by kodein.factory() // (2)
private val answerConstant: String by kodein.instance("answer") // (2)
fun someFunction() {
val dataSource: DataSource = kodein().instance() (3)
}
}-
Note the usage of
=and notby. -
Creating lazy properties (I am using a
LazyKodein, notKodeininstance). -
To access a
Kodeininstance, I usekodein().
You can create a LazyKodein with Kodein.lazy.
When doing so, even the bindings will be declared only when the first retrieval happens.
val kodein = Kodein.lazy { // (1)
println("doing bindings")
bind<DataSource>() with singleton { SqliteDS.open("path/to/file") }
}
class Controller() {
val ds: DataSource by kodein.instance()
fun someFunction() {
ds.open() // (2)
}
}-
The
kodeinobject is of typeLazyKodein, notKodein. -
Only there will "doing bindings" will be printed.
You can have classes that implement the interface LazyKodeinAware.
Doing so has the benefit of having a simpler syntax for lazy property creation.
class MyManager() : LazyKodeinAware {
override val kodein = LazyKodein { /* code to access a Kodein instance */ }
val ds: DataSource by instance()
}All methods that are available to LazyKodein are available to a LazyKodeinAware class.
Sometimes you need to retrieve objects that are dependent to the class of the object whose retrieval is for.
The most obvious example is loggers: you need loggers that will print the name of the class name of the class they are in.
First, you need to declare a binding to a factory that takes a Class as argument.
val kodein = Kodein {
bind<Logger>() with factory { cls: Class<*> -> LogManager.getLogger(cls) }
}Then, you can retrieve such bound types by using withClassOf.
class MyManager(val kodein: Kodein) {
val logger: Logger = kodein.withClassOf(this).instance()
}If you are using a Kodein aware class, a Kodein injected class or a lazy Kodein aware class, then it’s even easier: simply use withClass.
class MyManager(override val kodein: Kodein): KodeinAware {
val logger: Logger = withClass().instance()
}|
Note
|
You can use withClass for factories that take a Class<*> as parameter, and withKClass for factories that take a KClass<*> as parameter.
|
While Kodein does not allow you to declare modules or dependencies in Java, it does allow you to retrieve dependencies using a Java friendly API.
Simply give kodein.typed to your Java classes, and you can use Kodein in Java:
public class JavaClass {
private final Function1<Integer, Dice> diceFactory;
private final Datasource dataSource;
private final Function0<Random> randomProvider;
private final String answerConstant;
public JavaClass(TKodein kodein) {
diceFactory = kodein.factory(Integer.class, Dice.class);
dataSource = kodein.instance(Datasource.class);
randomProvider = kodein.provider(Random.class);
answerConstant = kodein.instance(String.class, "answer");
}
}|
Warning
|
Remember that Java is subject to type erasure.
Therefore, if you registered a generic Class binding such as Example: using TypeReference in Java
class JavaClass {
private final List<String> list;
public JavaClass(TKodein kodein) {
list = kodein.instance(new TypeReference<List<String>>(){});
}
} |
Maybe you want a Kodein instance that you can pass around and have different sections of your code configure its bindings.
Configurable Kodein is a Kodein extension that is not proposed by default, this paradigm is in a separate module.
|
Note
|
Using or not using this is a matter of taste and is neither recommended nor discouraged. |
ConfigurableKodein.fun test() {
val kodein = ConfigurableKodein()
kodein.addModule(apiModule)
kodein.addModule(dbModule)
val ds: DataSource = kodein.instance()
}<dependency>
<groupId>com.github.salomonbrys.kodein</groupId>
<artifactId>kodein-conf</artifactId>
<version>3.4.1</version>
</dependency>|
Note
|
Do not remove the "kodein" (or "kodein-erased") dependency. Both dependencies must be declared. |
You can import modules, extend kodein objects, or add bindings inside this ConfigurableKodein using addImport, addExtend and addConfig.
fun test() {
val kodein = ConfigurableKodein()
kodein.addModule(aModule)
kodein.addExtend(otherKodein)
kodein.addConfig {
bind<Dice>() with provider { RandomDice(0, 5) }
bind<DataSource>() with singleton { SqliteDS.open("path/to/file") }
}
}|
Caution
|
The Kodein object will effectively be constructed on first retrieval.
Once it is constructed, trying to configure it will throw an IllegalStateException.
|
You can use a ConfigurableKodein object like any Kodein object.
|
Caution
|
Once you have retrieved the first value with a ConfigurableKodein, trying to configure it will throw an IllegalStateException.
|
A ConfigurableKodein can be mutable.
val kodein = ConfigurableKodein(mutable = true)|
Warning
|
Using a mutable |
A mutable ConfigurableKodein can be configured even after first retrieval.
fun test() {
val kodein = ConfigurableKodein(mutable = true)
kodein.addModule(aModule)
val ds: DataSource = kodein.instance()
kodein.addModule(anotherModule) (1)
}-
This would have failed if the ConfigurableKodein was not mutable.
You can also use clear to remove all bindings.
Sometimes, you want one static Kodein for your entire application. E.g. you don’t want to have to hold & pass a Kodein instance throughout your application.
For these cases, the kodein-conf module proposes a static Kodein.global instance.
fun test() {
kodein.global.addModule(apiModule)
kodein.global.addModule(dbModule)
val ds: DataSource = kodein.global.instance()
}|
Caution
|
Just like any |
Kodein does work on Android!
You can use Kodein as-is in your Android project or use the very small util library kodein-android.
kodein-android:-
Add this line in your
dependenciesblock in your applicationbuild.gradlefile:compile 'com.github.salomonbrys.kodein:kodein:3.4.1' compile 'com.github.salomonbrys.kodein:kodein-android:3.4.1'
NoteBoth kodeinandkodein-androiddependencies must be declared.
WarningIf you are using kodein-erased, then you must declare both dependencies :kodein-erasedandkodein-android(but notkodein). -
If you are using Proguard, you need to add the following line to your proguard configuration file:
-keepattributes Signature
-
Declare the dependency bindings in the Android
Application, having it implementsKodeinAware.Example: an Android Application class that implements KodeinAwareclass MyApp : Application(), KodeinAware { override val kodein by Kodein.lazy { (1) /* bindings */ } }
-
Using Kodein.lazy allows you to access the
Contextat binding time.TipDon’t forget to declare the Application in the AndroidManifest.xmlfile!
-
-
In your Activities, Fragments, and other context aware android classes, retrieve dependencies!
There are different ways to access a Kodein instance and your dependencies.
appKodein is a property that will work in your context aware Android classes provided that your Application implements KodeinAware.
From it, you can construct a LazyKodein.
class MyActivity : Activity() {
val kodein = LazyKodein(appKodein)
val diceProvider: () -> Dice by kodein.provider() // (1)
override fun onCreate(savedInstanceState: Bundle?) {
val random: Random = kodein().instance() // (2)
}
}-
kodeinwithout parenthesis: creates a lazy property. -
kodeinwith parenthesis: gets the instance.
|
Warning
|
You cannot use kodein with parenthesis and access the Kodein instance while the activity is not initialized by Android.
|
Using an injector allows you to resolve all dependencies in onCreate, reducing the cost of dependency first-access (but more processing happening in onCreate).
class MyActivity : Activity() {
private val injector = KodeinInjector()
val random: Random by injector.instance()
override fun onCreate(savedInstanceState: Bundle?) {
injector.inject(appKodein())
}
}|
Note
|
Using this approach has an important advantage: as all dependencies are retrieved in onCreate, you can be sure that all your dependencies have correctly been retrieved, meaning that there were no non-declared dependency.If you only use instance (no provider or factory), you can also be sure that there were no dependency loop.
|
appKodein cannot be accessed before an activity has been created, before a fragment has been attached, and so on.
Because of this, it is not recommended to use KodeinAware in Android. Prefer using LazyKodeinAware or KodeinInjected.
class MyActivity : Activity(), LazyKodeinAware {
override val kodein = LazyKodein(appKodein)
val diceProvider: () -> Dice by provider()
}class MyActivity : Activity(), KodeinInjected {
override val injector = KodeinInjector()
val random: Random by instance()
override fun onCreate(savedInstanceState: Bundle?) {
inject(appKodein())
}
}To easily setup Kodein with your Android app, you can use the Android Injectors. They make it simple to creating activities, fragments, services, and broadcast recivers that work with Kodein out of the box.
|
Note
|
This method allows for deep Kodein integration into you Android components. You can choose to use Kodein without it. |
There are two ways to use them depending on your needs; inheritance based and interface based.
Both methods provide you with:
-
a
KodeinInjector(through theinjectorproperty) -
a binding for
KodeinInjected(which is the instance of your class) -
local bindings (bindings for that specific instance)
-
One example is a
KodeinActivityorActivityInjectorwill bindContextandActivityto itself, andFragmentManager,LoaderManager, andLayoutInflaterto its instances of those classes.
-
-
scope management (removing this component from the scope when it is destroyed so there are no memory leaks)
-
the ability to override previously defined bindings
|
Important
|
Don’t forget to use an Application that is KodeinAware:
|
class MyApplication : Application(), KodeinAware {
override val kodein by Kodein.lazy {
import(autoAndroidModule(this@MyApplication))
bind<String>("log-tag") with instance("MyApplication")
}
}Kodein provides base classes for the following Android components:
-
Activity (
KodeinActivity) -
FragmentActivity (
KodeinFragmentActivity) -
AppCompatActivity (
KodeinAppCompatActivity) -
Fragment (
KodeinFragment) -
Support v4 Fragment (
KodeinSupportFragment) -
Service (
KodeinService) -
IntentService (
KodeinIntentService) -
BroadcastReceiver (
KodeinBroadcastReceiver)
All it takes to get started is to extend one of those classes, and you’re ready to start injecting. Let’s see an example.
class MyActivity : KodeinActivity() { // (1)
private val logTag: String by instance("log-tag") // (2)
private val app: Application by injector.instance() // (3)
override fun provideOverridingModule() = Kodein.Module { // (4)
bind<MyActivity>() with instance(this@MyActivity)
bind<String>("log-tag", overrides = true) with instance("MyActivity")
}
override fun onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
Log.i(logTag, "Calling onCreate from MainActivity in ${app.applicationInfo.className}")
}
}-
Extending
KodeinActivityprovides us with aKodeinInjectorand takes care of its lifecycle -
KodeinActivityimplementsKodeinInjectedso we don’t need to use theinjectorproperty if we don’t want to -
We can also use the
injectorif we want to -
provideOverridingModuleallows us to override bindings specified higher up in the dependency tree (for example, we override the "log-tag"Stringbinding defined inMyApplication)
|
Caution
|
KodeinActivity, KodeinFragmentActivity, and KodeinAppCompatActivity will internally initialize their injector before they call super.onCreate (see the issue that necessitates this).
|
Kodein also provides a set of interfaces that provide the same functionality as the inheritance based method. The only difference is that the injector lifecycle must be managed. In almost every case, this can be accomplished by simply calling initializeInjector immediately after onCreate and destroyInjector immediately after onDestroy.
These are provided so that you can extend non-framework components if needed, because the JVM does not support multiple class inheritance.
The interfaces are:
-
ActivityInjector
-
FragmentActivityInjector
-
AppCompatActivityInjector
-
FragmentInjector
-
SupportFragmentInjector
-
ServiceInjector
-
IntentServiceInjector
-
BroadcastReceiverInjector
class MyFragment : CustomFragment(), FragmentInjector { // (1)
override val injector: KodeinInjector = KodeinInjector() // (2)
private val logTag: String by instance("log-tag") // (3)
private val app: Application by injector.instance() // (4)
override fun provideOverridingModule() = Kodein.Module { // (5)
bind<MyFragment>() with instance(this@MyFragment)
bind<String>("log-tag", overrides = true) with instance("MyFragment")
}
override fun onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
initializeInjector() // (6)
Log.i(logTag, "Calling onCreate from MainActivity in ${app.applicationInfo.className}")
}
override fun onDestroy() {
super.onDestroy()
destroyInjector() // (7)
}
}-
Because we extends
CustomFragmentwe cannot extendKodeinFragmentso instead we implementFragmentInjector -
We have to provide an injector (typically all that entails is just creating a new instance of
KodeinInjector) -
FragmentInjectorimplementsKodeinInjectedso we don’t need to use theinjectorproperty if we don’t want to -
We can also use the
injectorif we want to -
provideOverridingModuleallows us to override bindings specified higher up in the dependency tree (for example, we override the "log-tag"Stringbinding defined inMyApplication) -
Since we have to manage the injector’s lifecycle we initialize it when the fragment is initialized; in
onCreate -
Since we have to manage the injector’s lifecycle we destroy it when the fragment is destroyedl in
onDestroy
|
Caution
|
When using ActivityInjector, FragmentActivityInjector, or AppCompatActivityInjector it is suggested to call initializeInjector before super.onCreate (see the issue that necessitates this).
|
BroadcastReceiverInjector should be used like this:
class MyBroadcastReceiver : CustomBroadcastReceiver(), BroadcastReceiverInjector {
override val injector: KodeinInjector = KodeinInjector()
final override var context: Context? = null
final override fun onReceive(context: Context, intent: Intent) {
super.onReceive(context, intent)
this.context = context (1)
initializeInjector()
// do something
destroyInjector()
}
}-
It is necessary to set
BroadcastReceiverInjector.contextbefore callinginitializeInjector
The parent of any KodeinFragment, KodeinSupportFragment, FragmentInjector, or SupportFragmentInjector must be KodeinInjected. This means a parentFragment must be one of KodeinFragment, KodeinSupportFragment, FragmentInjector, or SupportFragmentInjector. If there is no parentFragment, the Activity of the Fragment must be one of KodeinActivity, KodeinFragmentActivity, KodeinAppCompatActivity, ActivityInjector, FragmentActivityInjector, or AppCompatActivityInjector.
|
Note
|
Inside a fragment, you can retrive a LayoutInflater using the tag ACTIVITY_LAYOUT_INFLATER.
Using the tag will use a more optimized way of retrieving the LayoutInflater.
|
Kodein-Android proposes a module that enables easy retrieval, with a context, of a lot of standard android services.
This module is absolutely optional, you are free to use it or leave it ;).
val kodein = Kodein {
import(androidModule)
/* other bindings */
}You can see everything that this module proposes in the AndroidModule.kt file.
To retrieve instances of bindings defined in this module, you can use withContext.
class MyActivity : Activity(), LazyKodeinAware {
override val kodein = LazyKodein(appKodein)
val inflater: LayoutInflater by withContext(this).instance()
}There is also an "auto" version of the module.
class MyApplication : Application(), KodeinAware {
override val kodein by Kodein.lazy {
import(autoAndroidModule(this@MyApplication))
/* other bindings */
}
}Retrieving instances of bindings from the autoAndroidModule does not require a Context.
class MyActivity : Activity(), LazyKodeinAware {
override val kodein = LazyKodein(appKodein)
val inflater: LayoutInflater by instance()
}You can see everything that this module proposes in the AndroidModule.kt file.
There are times where you need an object to be a singleton, but only during the lifetime of a Context. You can use the contextSingleton scope to achieve this.
|
Note
|
The context scope should be used when a binding could apply to either an Activity or a Service. When a binding is exclusively for an Activity or a Service, the activity or service scope should be used instead.
|
val kodein = Kodein {
bind<Logger>() with scopedSingleton(androidContextScope) { LogManager.getNamedLogger(it.localClassName) } // (1)
}-
itis the context the object is being created for.
To retrieve an object bound in the context scope, you need to inject a factory that takes the context as a parameter.
val logger: Logger = kodein.with(context).instance()
val sameLogger: Logger = kodein.with(context).instance() // this will be the same object as logger
val otherLogger: Logger = kodein.with(otherContext).instance() // this will be a different object than logger|
Note
|
The activity and service scope are special cases of the context scope. The bindings returned for an Activity or Service object from the context scope will be the same one returned for that object from the activity or service scope
|
If you want a singleton that lives only during the lifecycle of a specific Activity, and not any Context, you can use the activity scope.
val kodein = Kodein {
bind<Logger>() with scopedSingleton(androidActivityScope) { LogManager.getNamedLogger(it.localClassName) } // (1)
}-
itis the activity the object is being created for.
As with the context scope, to retrieve objects bound in the activity scope, you need to inject a factory which takes the activity as a parameter.
val logger: Logger = kodein.with(getActivity()).instance()If you don’t want to be required to explicitly provide an activity instance to inject your objects, you can use the "auto activity scope".
val kodein = Kodein {
bind<Logger>() with autoScopedSingleton(androidActivityScope) { LogManager.getNamedLogger(it.localClassName) }
}val logger: Logger = kodein.instance()|
Warning
|
In your Example: registering kodein’s lifecycle manager to enable the auto activity scope to work
class MyApplication : Application {
override fun onCreate() {
registerActivityLifecycleCallbacks(androidActivityScope.lifecycleManager) // (1)
}
}
|
|
Caution
|
Objects that are bound in the auto androidActivityScope will always be injected according to the last displayed activity.
|
If you want a singleton that lives only during the lifecycle of a specific Service, and not any Context, you can use the service scope.
val kodein = Kodein {
bind<Logger>() with scopedSingleton(androidServiceScope) { LogManager.getNamedLogger(it.localClassName) } // (1)
}-
itis the service the object is being created for.
As with the context scope, to retrieve objects bound in the service scope, you need to inject a factory which takes the service as a parameter.
val logger: Logger = kodein.with(service).instance()If you want a singleton that lives only during the lifecycle of a specific BroadcastReceiver, you can use the broadcast receiver scope.
val kodein = Kodein {
bind<Logger>() with scopedSingleton(androidBroadcastReceiverScope) { LogManager.getNamedLogger(it.localClassName) } // (1)
}-
itis the broadcast receiver the object is being created for.
To retrieve objects bound in the broadcast receiver scope, you need to inject a factory which takes the broadcast receiver as a parameter.
val logger: Logger = kodein.with(broadcastReceiver).instance()If you want a singleton that lives only during the lifecycle of a specific Fragment, you can use the fragment scope (or support fragment scope if you are using support lib fragments).
val kodein = Kodein {
bind<Logger>() with scopedSingleton(androidFragmentScope) { LogManager.getNamedLogger(it.localClassName) } // (1)
}-
itis the fragment the object is being created for.
val kodein = Kodein {
bind<Logger>() with scopedSingleton(androidSupportFragmentScope) { LogManager.getNamedLogger(it.localClassName) } // (1)
}-
itis the support fragment the object is being created for.
To retrieve objects bound in the fragment scope (or support fragment scope), you need to inject a factory which takes the fragment as a parameter.
val logger: Logger = kodein.with(fragment).instance()val logger: Logger = kodein.with(supportFragment).instance()Have a look at the Android demo project!
You can easily print bindings with println(kodein.container.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>("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.
|
Note
|
Descriptions prints type names in a "kotlin-esque" way.
Because Kodein does not depends on kotlin-reflect, it uses java Type objects that do not contains nullability information.
As such, the type display does not include nullability. Still, it’s easier to read List<*> than List<? extends Object>.
|
When it detects a recursive dependency, Kodein will throw a Kodein.DependencyLoopException.
The message of the exception explains how the loop happened.
com.github.salomonbrys.kodein.Kodein$DependencyLoopException: Dependency recursion:
╔═> bind<com.test.A>()
╠─> bind<com.test.B>() // (1)
╠─> bind<com.test.C>("yay") // (2)
╚═> bind<com.test.A>() // (3)
-
com.test.Adepends oncom.test.B -
com.test.Bdepends oncom.test.Cwith the tag "Yay" -
com.test.Cwith the tag "Yay" depends oncom.test.A, we have found the dependency loop!.
By default, 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, kodein relies heavily on the generic function, which is real a performance pitfall.
To improve performance, you can use the erased* set of kodein functions, which are faster, but do suffer from type erasure!
|
Warning
|
Yes, #perfmatters. However, the humble opinion of the author is that:
Therefore, please make sure that, using the |
Each kodein function that handles a type exists in two form: generic and erased.
For example, the kodein.instance function exists as kodein.genericInstance and kodein.erasedInstance.
By default, all type functions are aliases to their "generic*" counterpart.
For example, the kodein.instance function is an alias to kodein.genericInstance
So, when you know that you inject a type that is not generic, you can use kodein.erasedInstance:
val session: HttpSession = kodein.erasedInstance()If you know what you are doing, and why you are doing it, you can change the default methods to alias the erased* function set.
For this, you must not depend on the kodein module, and instead, depend on the kodein-erased module.
|
Caution
|
This means that, by default, kodein.instance<List<String>>() will look for an erased List binding.Therefore, to bind a List<String> you must use bindGeneric<List<String>>(), and to retrieve it, you must use kodein.genericInstance().
|
|
Note
|
The erased* function set is located in a different package than the regular one: com.github.salomonbrys.kodein.erased.
|
Kodein offers a module that implements reflexivity injection based on the javax.inject.* annotations.
There are two reasons to use this module:
-
You are moving a code base from a Java injection library (such as Guice or Dagger) and want the Java code to work the same while their still is injected java code.
-
You want to easilly use Kodein in a Java code.
-
That’s it!
Using this module with Kotlin code means adding a lot of reflexivity at run-time that can easily be avoided in Kotlin (but not in Java).
|
Caution
|
Every-thing that is described here is a lot less performant than using classic kodein injection methods. PLEASE DO NOT USE ON KOTLIN CLASSES. Kitties will die painfully if you do! |
<dependency>
<groupId>com.github.salomonbrys.kodein</groupId>
<artifactId>kodein-conf</artifactId>
<version>3.4.1</version>
</dependency>|
Note
|
Do not remove the "kodein" (or "kodein-erased") dependency. Both dependencies must be declared. |
Then, import the module.
compile 'com.github.salomonbrys.kodein:kodein-jxinject:3.4.1'|
Note
|
Do not remove the "kodein" (or "kodein-erased") dependency. Both dependencies must be declared. |
Then, import the module.
You can create a new instance of a given class, provided that:
-
The class has only one constructor
-
Or the class have one of its constructors annotated with
@Inject.
It is a good practice, however, to always have an @Inject constructor, even if it is the only constructor.
public class MyJavaController {
@Inject
public MyJavaController(Connection connection, FileSystem fs) {
/* ... */
}
/* ... */
}You can then create instances of such classes by using kodein.jx in Kotlin, or Jx.of(kodein) in Java.
val controller = kodein.jx.newInstance<MyJavaController>()MyJavaController controller = Jx.of(kodein).newInstance(MyJavaController.class);You can inject fields of a class by annotating them with @Inject.
public class MyJavaController {
@Inject
Connection connection;
@Inject
FileSystem fs;
/* ... */
}You can then inject existing instances of such classes by using kodein.jx in Kotlin, or Jx.of(kodein) in Java.
val controller = MyJavaController()
kodein.jx.inject(controller)MyJavaController controller = new MyJavaController();
Jx.of(kodein).inject(controller);|
Warning
|
Method injection is supported to be compatible with Java injection libraries. It is, however, widely considered as the less semantic injection method. |
You can have @Inject annotated mehtod be called at injection.
public class MyJavaController {
@Inject
public setIO(Connection connection, FileSystem fs) {
/* ... */
}
/* ... */
}You know the drill, use kodein.jx in Kotlin or Jx.of(kodein) in Java the exact same way as for field injection.
javax.inject libraries use the concept of "qualifier annotations", which serves the same purpose as Kodein’s tag system.
The @Named annotation is a qualifier provided by default, and is supported by default in Kodein-JxInject.
In Java, any field or method / constructor parameter annotated with @Named("whatever") will use the String value as tag.
public class MyJavaController {
@Inject @Named("SQL")
Connection connection; (1)
@Inject setConnection(@Named("SQL") Connection connection) { /*...*/ } (2)
}<1>: Field injection. <2>: Method injection.
To inject the connection field, Kodein will essentially retrive as kodein.instance<Connection>(tag = "SQL").
For any other qualifier annotation, you need to provide a function that will transform a qualifier annotation to a tag.
val kodein = Kodein {
import(jxInjectorModule)
/* Other bindings */
jxQualifier<MyQualifier> { MyTag(it.value) } (1)
}<1>: Transforms a MyQualifier qualifier annotation into a MyTag Kodein tag.
If you need to inject erased binding, you can annotate the field or method / constructor parameter with the @ErasedBinding annotation.
public class MyJavaController {
@Inject @ErasedBinding List<Connection> connections;
}If you need to inject something only if it was bound (and set it to null otherwise), you can use the @OrNull annotation.
public class MyJavaController {
@Inject @OrNull Connection connectionOrNull;
}You can inject a provider, either by using javax.inject.Provider or kotlin.jvm.functions.Function0.
Note that if you are using the latter, you need to use the @ProviderFun annotation.
public class MyJavaController {
@Inject Provider<Connection> connectionJXProvider;
@Inject @ProviderFun Function0<Connection> connectionKotlinProvider;
}To inject a factory, you need to use kotlin.jvm.functions.Function1 annotated with @FactoryFun.
public class MyJavaController {
@Inject @ProviderFun Function0<String, Connection> connectionFactory;
}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 {
import(engineModule)
onReady {
val engine = instance<Engine>()
instance<Logger>().info("Starting engine version ${engine.version}")
engine.start()
}
}Scoped singleton are singletons that are bound to a context and live while that context exists.
To define a scope that can contain scoped singleton, 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.
Standard ways of doing so is to use the userData property of the context, if is has one, or else to use a WeakHashMap<C, ScopeRegistry>.
To declare bindings in your scope, use scopedSingleton.
object myScope: Scope<Request> { // (1)
override fun getRegistry(context: Request): ScopeRegistry
= context.userData.getOrPut("registry") { ScopeRegistry() } as ScopeRegistry // (2)
}
val kodein = Kodein {
bind<Logger>() with scopedSingleton(myScope) { LogManager.getNamedLogger(it.name) } // (3)
}-
The scope’s context type is
Request. -
Creates a
ScopeRegistryin the contextRequestif there is none. -
itis the context. There will be at most oneLoggerperRequestobject.
To retrieve a scoped singleton bound type, you must retrieve a factory and then provide it the context.
val logger: Logger = kodein.with(getRequest()).instance()Scoped singletons are not always ideal since you need the context to retrieve any object.
Sometimes, the context is static.
For these times, you can use an "auto scoped singleton".
An auto scoped singleton is responsible for fetching both the ScopeRegistry and the context.
To define an auto scope that can contain auto scoped singleton, you must define an object that implements the AutoScope interface.
To declare bindings in your scope, use autoScopedSingleton.
object myScope: AutoScope<Request> { // (1)
override fun getRegistry(context: Request): ScopeRegistry
= context.userData.getOrPut("registry") { ScopeRegistry() } as ScopeRegistry // (2)
override fun getContext(): Request
= StaticContext.getCurrentRequest() // (3)
}
val kodein = Kodein {
bind<Logger>() with autoScopedSingleton(::myScope) { LogManager.getNamedLogger(it.name) } // `it` is the context.
}-
The scope’s context type is
Request. -
Same as
Scope.getRegistry. -
Get the context from a static environment.
To retrieve an auto scoped singleton bound type, you can retrieve a provider or an instance.
val logger: Logger = kodein.instance()|
Tip
|
If your auto scope does not depends on a context, and always yields the same Example: defining a static auto scope
object myScope: AutoScope<Unit> {
private val _registry = ScopeRegistry()
override fun getRegistry(context: Unit) = _registry
override fun getContext() = Unit
}
|
A factory function is an extension function to Kodein.Builder that returns a Factory<A, T>. You can use the CFactory<A, T> class for ease of use.
If your scope is a provider scope (such as singleton), you can use the CProvider<T> class for ease of use.
Have a look at existing scopes in the factories.kt file. The singleton scope is very easy to understand and is a good starting point.
Accessing and using kodein.typed is not reserved to Java.
You can use it in Kotlin to access an API that directly uses Type, TypeToken or Class objects.
In fact, most Kodein extension functions such as kodein.instance<Type>() are inline methods that proxy to this typed API.
When defining bindings, in the Kodein.Builder, you can access the typed property to bind factories to a Type, a TypeToken or a Class.
A KodeinInjector also provides a typed API, simply use injector.typed.
Kodein almost never uses java classes, because they are subject to type erasure.
To get a real type, Kodein uses the generic function.
The generic function produces a TypeToken object, which only contains a type.
-
generic<String>().typeis the String’s javaClass. -
generic<List<String>>.typeis aParameterizedTypedescribing exactly aList<String>.
TypeToken is a generic interface, that way, even if it’s type is a weird type, functions that use a TypeToken (such as the typed API) can preserve type safety.
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 bindings are not ([BindType], [Tag]) but actually ([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.
|
Caution
|
Please be cautious when using this knowledge, as other less thorough readers may get confused with it. |
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 Factory is bound to a Kodein.Key.
In fact, all Kodein.typed 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.
You can access a copy of the bindings map with kodein.container.bindings.
From this Map<Kodein.Key, Factory<*, *>>, you can explore all bindings, their keys and factories.
|
Tip
|
The bindings.kt file exposes several extension functions to this map that can be useful for exploring it. |
The API reference can be found here!
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 README3.adoc. (The documentation page is auto-generated from it).
You’ve read so far?! You’re awesome!
Why don’t you drop by the Kodein Slack channel on Kotlin’s Slack group?
Kodein is free to use for both non-profit and commercial use and always will be.
If you wish to show some support or appreciation to my work, you are free to donate!
|
Tip
|
This would be (of course) greatly appreciated but is by no means necessary to receive help or support, which I’ll be happy to provide for free! |
