KOtlin DEpendency INjection - A straightforward and yet very useful dependency retrieval container for Kotlin Multiplatform
npx @tessl/cli install tessl/maven-org-kodein-di--kodein-di@7.26.0Kodein-DI is a comprehensive dependency injection framework for Kotlin Multiplatform applications that provides effortless dependency retrieval and container management. It enables lazy instantiation of dependencies, eliminates concerns about initialization order, and supports binding classes or interfaces to their instances or providers with a declarative DSL.
implementation("org.kodein.di:kodein-di:7.26.1")import org.kodein.di.*For specific functionality:
import org.kodein.di.DI
import org.kodein.di.DIAware
import org.kodein.di.DirectDI
import org.kodein.di.bindSingleton
import org.kodein.di.bindProvider
import org.kodein.di.bindFactory
import org.kodein.di.instance
import org.kodein.di.provider
import org.kodein.di.factoryimport org.kodein.di.*
// Define interfaces and classes
interface DataSource
class SqliteDataSource : DataSource
interface UserService
class UserServiceImpl(private val dataSource: DataSource) : UserService
// Create DI container
val di = DI {
bind<DataSource>() with singleton { SqliteDataSource() }
bind<UserService>() with provider { UserServiceImpl(instance()) }
constant("appName") with "MyApp"
}
// Use dependency injection
class MyController : DIAware {
override val di: DI = di
private val userService: UserService by instance()
private val appName: String by constant()
fun handleRequest() {
// Use injected dependencies
println("Handling request in $appName")
}
}
// Direct access (immediate retrieval)
val directDI = di.direct
val userService = directDI.instance<UserService>()Kodein-DI is built around several key components:
Core DI container creation and configuration with binding DSL for defining how dependencies are created and managed.
interface DI : DIAware {
val container: DIContainer
companion object {
operator fun invoke(
allowSilentOverride: Boolean = false,
init: MainBuilder.() -> Unit
): DI
fun lazy(
allowSilentOverride: Boolean = false,
init: MainBuilder.() -> Unit
): LazyDI
fun direct(
allowSilentOverride: Boolean = false,
init: MainBuilder.() -> Unit
): DirectDI
fun withDelayedCallbacks(
allowSilentOverride: Boolean = false,
init: MainBuilder.() -> Unit,
): Pair<DI, () -> Unit>
fun from(modules: List<Module>): DI
var defaultFullDescriptionOnError: Boolean
var defaultFullContainerTreeOnError: Boolean
}
}
data class Module(
val allowSilentOverride: Boolean = false,
val prefix: String = "",
val init: Builder.() -> Unit
) {
val name: String
constructor(
name: String,
allowSilentOverride: Boolean = false,
prefix: String = "",
init: Builder.() -> Unit,
)
operator fun getValue(thisRef: Any?, property: KProperty<*>): Module
}Declarative syntax for binding types to their implementations with various creation patterns including singleton, provider, factory, instance, and multiton.
interface DI.Builder {
fun <T : Any> Bind(type: TypeToken<out T>, tag: Any? = null, overrides: Boolean? = null): TypeBinder<T>
fun <T : Any> Bind(tag: Any? = null, overrides: Boolean? = null, binding: DIBinding<*, *, T>)
fun constant(tag: Any, overrides: Boolean? = null): ConstantBinder
fun <T : Any> Delegate(type: TypeToken<out T>, tag: Any? = null, overrides: Boolean? = null): DelegateBinder<T>
fun import(module: Module, allowOverride: Boolean = false)
fun importAll(vararg modules: Module, allowOverride: Boolean = false)
fun importOnce(module: Module, allowOverride: Boolean = false)
fun onReady(cb: DirectDI.() -> Unit)
// Set binding methods
fun <T : Any> BindInSet(tag: Any? = null, overrides: Boolean? = null, type: TypeToken<out T>, creator: SetBinder<T>.() -> Unit)
fun <T : Any> InBindSet(tag: Any? = null, overrides: Boolean? = null, type: TypeToken<out T>, creator: SetBinder<T>.() -> Unit)
fun <T : Any> AddBindInSet(tag: Any? = null, overrides: Boolean? = null, binding: DIBinding<*, *, T>)
}
fun <T : Any> DI.BindBuilder<*>.singleton(
creator: NoArgBindingDI<*>.() -> T
): Singleton<*, T>
fun <T : Any> DI.BindBuilder<*>.provider(
creator: NoArgBindingDI<*>.() -> T
): Provider<*, T>
fun <A, T : Any> DI.BindBuilder<*>.factory(
creator: BindingDI<*>.(A) -> T
): Factory<*, A, T>Property delegation pattern for lazy dependency retrieval using DIAware interface with automatic initialization and caching.
interface DIAware {
val di: DI
val diContext: DIContext<*>
val diTrigger: DITrigger?
}
fun <T : Any> DIAware.instance(tag: Any? = null): LazyDelegate<T>
fun <T : Any> DIAware.provider(tag: Any? = null): LazyDelegate<() -> T>
fun <A, T : Any> DIAware.factory(tag: Any? = null): LazyDelegate<(A) -> T>
fun <T : Any> DIAware.constant(): LazyDelegate<T>Immediate dependency retrieval without property delegation for cases requiring direct access to dependencies.
interface DirectDI : DirectDIBase {
fun <T : Any> Instance(type: TypeToken<T>, tag: Any? = null): T
fun <T : Any> Provider(type: TypeToken<T>, tag: Any? = null): () -> T
fun <A, T : Any> Factory(argType: TypeToken<in A>, type: TypeToken<T>, tag: Any? = null): (A) -> T
fun On(context: DIContext<*>): DirectDI
}
interface DirectDIAware {
val directDI: DirectDI
}Context-aware dependency management with support for hierarchical scopes, context translation, and lifecycle management.
interface DIContext<C : Any> {
val type: TypeToken<in C>
val value: C
data class Value<C : Any>(override val type: TypeToken<in C>, override val value: C) : DIContext<C>
class Lazy<C : Any>(override val type: TypeToken<in C>, val getValue: () -> C) : DIContext<C>
}
interface Scope<C> {
fun getRegistry(context: C): ScopeRegistry
}
interface ContextTranslator<C, S> {
fun translate(key: DI.Key<*, *, *>, context: C): S?
}Advanced dependency injection patterns including constructor injection, external sources, binding search, and sub-DI creation.
fun <T> DirectDIAware.new(constructor: () -> T): T
fun <P1, T> DirectDIAware.new(constructor: (P1) -> T): T
interface ExternalSource {
fun <C : Any, A, T : Any> getFactory(
key: DI.Key<C, A, T>,
context: C
): ((A) -> T)?
}
fun DirectDIAware.subDI(
allowSilentOverride: Boolean = false,
copy: Copy = Copy.NonCached,
init: DI.MainBuilder.() -> Unit
): DIKodein-DI defines several specific exception types for different error conditions:
class DI.NotFoundException(val key: Key<*, *, *>, message: String) : RuntimeException(message)
class DI.DependencyLoopException(message: String) : RuntimeException(message)
class DI.OverridingException(message: String) : RuntimeException(message)
class DI.NoResultException(val search: SearchSpecs, message: String) : RuntimeException(message)
class DI.UnusedParameterException(message: String, cause: Exception? = null) : RuntimeException(message, cause)data class DI.Key<in C : Any, in A, out T : Any>(
val contextType: TypeToken<in C>,
val argType: TypeToken<in A>,
val type: TypeToken<out T>,
val tag: Any?
)
interface Typed<A> {
val type: TypeToken<A>
val value: A
}