Advanced knowledge
Debugging
Print bindings
You can easily print bindings with println(di.container.tree.bindings.description)
.
Here’s an example of what this prints:
bind<Dice> { factory { Int -> RandomDice } } bind<DataSource> { singleton { SQLiteDataSource } } bind<Random> { provider { SecureRandom } } bind<String>(tag = "answer") { 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-DI 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-DI will throw a DI.DependencyLoopException
.
The message of the exception explains how the loop happened.
DI$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!. |
Dependency not found
When you try to inject / retrieve a dependency that does not exists, Kodein-DI will throw a DI.NotFoundException
.
The message of the exception explains what is the binding the container is looking for.
No binding found for bind<Person> { ? { ? } }
If you need to trace what are the different bindings available in the container you can use the option fullContainerTreeOnError = true
, to log them all.
DI.NotFoundException
.val di = DI.direct { fullContainerTreeOnError = true (1) bind<A> { singleton { A(instance()) } } (2) } di.instance<B>() (3)
1 | Enables the option. |
2 | Any bindings. |
3 | Try to retrieve an unbound type |
No binding found for bind<B> { ? { ? } } Registered in this DI container: bind<A> { singleton { A } }
OnReady callbacks
You can define callbacks to be called once the di instance is ready and all bindings are defined. This can be useful to do some "starting" jobs.
val appModule = DI.Module(name = "app") {
import(engineModule)
onReady {
val engine = instance<Engine>()
instance<Logger>().info("Starting engine version ${engine.version}")
engine.start()
}
}
Using generic and erased function forms
Each DI function that handles a type exists in two form: as inline (lowercased first letter) and as regular function (uppercased first letter).
For example, the di.instance
function also exists as di.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 di.Instance(erased())
By default, all inline functions are aliases to their uppercase counterparts using the generic
function.
For example, the di.instance()
function is an alias to di.Instance(generic())
So, when you know that you inject a type that is not generic, you can use di.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-DI provides a way to represent a generic type in an erased way:
erasedComp(Set::class, erased(String::class)) // Represents a Set<String>
erasedComp(Map::class, erased(Int::class), erased(String::class)) // Represents a Map<Int, String>
erasedComp(Triple::class, erased(Int::class), erased(String::class), erased(Int::class)) // 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 DIContainer is the sacred DI object that contains all bindings and is responsible for retrieval.
You can access it with di.container
.
In it, each Binding
is bound to a DI.Key
.
In fact, all DI functions are proxies to this container API.
When defining bindings, in the DI.Builder
, you can access the container
property to bind factories to a DI.Key
or a DI.Bind
.