KOtlin DEpendency INjection - A straightforward and yet very useful dependency retrieval container for Kotlin Multiplatform
—
Property delegation pattern for lazy dependency retrieval using the DIAware interface with automatic initialization, caching, and optional triggers for controlling when dependencies are resolved.
Core interface that enables lazy property delegation for dependency injection with context and trigger support.
/**
* Base interface for classes that use lazy dependency injection
*/
interface DIAware {
/** Reference to the DI container */
val di: DI
/** Context for scoped dependency resolution */
val diContext: DIContext<*>
/** Optional trigger controlling when dependencies are resolved */
val diTrigger: DITrigger?
}
/**
* Property delegate interface for lazy dependency resolution
* @param V Type of the value provided by the delegate
*/
interface LazyDelegate<V> {
/** Retrieve the value (may trigger initialization) */
operator fun getValue(thisRef: Any?, property: KProperty<*>): V
/** Create the property delegate */
operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): LazyDelegate<V>
/** Check if the value has been initialized */
fun isInitialized(): Boolean
}
/**
* Trigger interface for controlling dependency resolution timing
*/
interface DITrigger {
/** Trigger the resolution process */
fun trigger()
}Lazy property delegates for retrieving single instances of dependencies with automatic caching and initialization.
/**
* Get a lazy delegate for an instance of type T
* @param tag Optional tag for disambiguation
* @return LazyDelegate that resolves to an instance of T
*/
fun <T : Any> DIAware.instance(tag: Any? = null): LazyDelegate<T>
/**
* Get a lazy delegate for an instance with explicit type token
* @param type TypeToken representing the type to retrieve
* @param tag Optional tag for disambiguation
* @return LazyDelegate that resolves to an instance of T
*/
fun <T : Any> DIAware.Instance(type: TypeToken<out T>, tag: Any? = null): LazyDelegate<T>
/**
* Get a lazy delegate for an instance with factory argument
* @param argType TypeToken for the argument type
* @param type TypeToken for the return type
* @param tag Optional tag for disambiguation
* @param arg Function providing the argument for the factory
* @return LazyDelegate that resolves to an instance of T
*/
fun <A, T : Any> DIAware.Instance(
argType: TypeToken<in A>,
type: TypeToken<T>,
tag: Any? = null,
arg: () -> A
): LazyDelegate<T>
/**
* Get a lazy delegate for a nullable instance
* @param tag Optional tag for disambiguation
* @return LazyDelegate that resolves to an instance of T or null if not found
*/
fun <T : Any> DIAware.instanceOrNull(tag: Any? = null): LazyDelegate<T?>
/**
* Get a lazy delegate for a nullable instance with explicit type token
* @param type TypeToken representing the type to retrieve
* @param tag Optional tag for disambiguation
* @return LazyDelegate that resolves to an instance of T or null if not found
*/
fun <T : Any> DIAware.InstanceOrNull(type: TypeToken<out T>, tag: Any? = null): LazyDelegate<T?>Lazy property delegates for retrieving provider functions that create new instances each time they are called.
/**
* Get a lazy delegate for a provider function
* @param tag Optional tag for disambiguation
* @return LazyDelegate that resolves to a function () -> T
*/
fun <T : Any> DIAware.provider(tag: Any? = null): LazyDelegate<() -> T>
/**
* Get a lazy delegate for a provider with explicit type token
* @param type TypeToken representing the type to provide
* @param tag Optional tag for disambiguation
* @return LazyDelegate that resolves to a function () -> T
*/
fun <T : Any> DIAware.Provider(type: TypeToken<out T>, tag: Any? = null): LazyDelegate<() -> T>
/**
* Get a lazy delegate for a provider curried from a factory
* @param argType TypeToken for the argument type
* @param type TypeToken for the return type
* @param tag Optional tag for disambiguation
* @param arg Function providing the argument for currying
* @return LazyDelegate that resolves to a provider function
*/
fun <A, T : Any> DIAware.Provider(
argType: TypeToken<in A>,
type: TypeToken<out T>,
tag: Any? = null,
arg: () -> A
): DIProperty<() -> T>
/**
* Get a lazy delegate for a nullable provider function
* @param tag Optional tag for disambiguation
* @return LazyDelegate that resolves to a function (() -> T)? or null if not found
*/
fun <T : Any> DIAware.providerOrNull(tag: Any? = null): LazyDelegate<(() -> T)?>
/**
* Get a lazy delegate for a nullable provider with explicit type token
* @param type TypeToken representing the type to provide
* @param tag Optional tag for disambiguation
* @return LazyDelegate that resolves to a function (() -> T)? or null if not found
*/
fun <T : Any> DIAware.ProviderOrNull(type: TypeToken<out T>, tag: Any? = null): LazyDelegate<(() -> T)?>Lazy property delegates for retrieving factory functions that take arguments and create instances.
/**
* Get a lazy delegate for a factory function
* @param tag Optional tag for disambiguation
* @return LazyDelegate that resolves to a function (A) -> T
*/
fun <A, T : Any> DIAware.factory(tag: Any? = null): LazyDelegate<(A) -> T>
/**
* Get a lazy delegate for a factory with explicit type tokens
* @param argType TypeToken for the argument type
* @param type TypeToken for the return type
* @param tag Optional tag for disambiguation
* @return LazyDelegate that resolves to a function (A) -> T
*/
fun <A, T : Any> DIAware.Factory(
argType: TypeToken<in A>,
type: TypeToken<out T>,
tag: Any? = null
): LazyDelegate<(A) -> T>
/**
* Get a lazy delegate for a nullable factory function
* @param tag Optional tag for disambiguation
* @return LazyDelegate that resolves to a function ((A) -> T)? or null if not found
*/
fun <A, T : Any> DIAware.factoryOrNull(tag: Any? = null): LazyDelegate<((A) -> T)?>
/**
* Get a lazy delegate for a nullable factory with explicit type tokens
* @param argType TypeToken for the argument type
* @param type TypeToken for the return type
* @param tag Optional tag for disambiguation
* @return LazyDelegate that resolves to a function ((A) -> T)? or null if not found
*/
fun <A, T : Any> DIAware.FactoryOrNull(
argType: TypeToken<in A>,
type: TypeToken<out T>,
tag: Any? = null
): LazyDelegate<((A) -> T)?>Lazy property delegates for retrieving constant values using property names as tags automatically.
/**
* Get a lazy delegate for a constant using the property name as tag
* @return LazyDelegate that resolves to the constant value
*/
fun <T : Any> DIAware.constant(): LazyDelegate<T>
/**
* Get a lazy delegate for a nullable constant using the property name as tag
* @return LazyDelegate that resolves to the constant value or null if not found
*/
fun <T : Any> DIAware.constantOrNull(): LazyDelegate<T?>Wrapper class that automatically uses property names as tags for all dependency lookups.
/**
* Wrapper class that uses property names as tags automatically
*/
@JvmInline
value class Named(private val base: DIAware) : DIAware by base
/**
* Get a named wrapper that uses property names as tags
*/
val DIAware.named: Named
// Named delegates (same signatures as DIAware but use property name as tag)
fun <T : Any> Named.instance(): LazyDelegate<T>
fun <T : Any> Named.provider(): LazyDelegate<() -> T>
fun <A, T : Any> Named.factory(): LazyDelegate<(A) -> T>
// ... all other delegate methods without tag parametersAdvanced property delegation with custom contexts and triggers for fine-grained control over dependency resolution.
/**
* Create a new DIAware instance with different context and/or trigger
* @param context New context for scoped dependencies
* @param trigger New trigger for controlling resolution timing
* @return New DIAware instance with updated context/trigger
*/
fun DIAware.On(context: DIContext<*> = this.diContext, trigger: DITrigger? = this.diTrigger): DI
/**
* Create a new DIAware instance with typed context
* @param context Context value
* @param trigger Optional trigger for controlling resolution timing
* @return New DIAware instance with typed context
*/
fun <C : Any> DIAware.on(context: C, trigger: DITrigger? = this.diTrigger): DI
/**
* Create a new instance using dependency injection within a closure
* @param creator Function that creates the instance using DirectDI
* @return LazyDelegate that creates the instance when accessed
*/
fun <T> DIAware.newInstance(creator: DirectDI.() -> T): LazyDelegate<T>Internal implementation classes for property delegation (typically not used directly).
/**
* Property delegate implementation (internal)
* @param V Type of the delegated value
*/
class DIProperty<V>(
trigger: DITrigger?,
context: DIContext<*>,
originalContext: DIContext<*>,
f: (DIContext<*>, KProperty<*>) -> V
) : LazyDelegate<V>
/**
* Mapped property delegate that transforms values
* @param I Input type from the original delegate
* @param O Output type after transformation
*/
class DIPropertyMap<I, O>(
private val original: LazyDelegate<I>,
private val map: (I) -> O
) : LazyDelegate<O>Usage Examples:
// Basic DIAware implementation
class UserController : DIAware {
override val di: DI = appDI
// Instance delegates - singleton access
private val userService: UserService by instance()
private val database: Database by instance()
// Provider delegates - new instance each access
private val emailService: () -> EmailService by provider()
private val loggerProvider: () -> Logger by provider("fileLogger")
// Factory delegates - parameterized creation
private val userRepositoryFactory: (String) -> UserRepository by factory()
private val cacheFactory: (String) -> Cache by factory()
// Constant delegates using property name as tag
private val apiUrl: String by constant()
private val timeout: Long by constant()
// Nullable delegates for optional dependencies
private val analyticsService: AnalyticsService? by instanceOrNull()
private val debugLogger: (() -> Logger)? by providerOrNull("debug")
fun handleUser(userId: String) {
val service = userService // Singleton instance
val email = emailService() // New instance each call
val repo = userRepositoryFactory(userId) // Factory with argument
val cache = cacheFactory("user:$userId") // Another factory call
// Use constant values
val url = apiUrl // Resolves to constant tagged "apiUrl"
val timeoutMs = timeout // Resolves to constant tagged "timeout"
// Optional services
analyticsService?.track("user_accessed", userId)
debugLogger?.invoke()?.debug("Processing user $userId")
}
}
// Named property access (uses property names as tags)
class ConfigService : DIAware {
override val di: DI = configDI
// These automatically use property names as tags
private val databaseUrl: String by named.instance()
private val apiKey: String by named.instance()
private val retryCount: Int by named.instance()
private val enableLogging: Boolean by named.instance()
}
// Custom context and triggers
class ScopedController : DIAware {
override val di: DI = appDI
override val diContext: DIContext<*> = diContext<UserSession> { getCurrentSession() }
override val diTrigger: DITrigger = object : DITrigger {
override fun trigger() {
// Custom trigger logic - maybe validate session
validateCurrentSession()
}
}
// These will use the custom context and trigger
private val sessionData: SessionData by instance()
private val userPreferences: UserPreferences by instance()
// Create instances with dependency injection
private val processor: RequestProcessor by newInstance {
RequestProcessor(instance(), instance(), constant("maxRetries"))
}
// Change context for specific dependencies
private val globalCache: Cache by On(AnyDIContext).instance("global")
}
// Late initialization with property delegates
class Application : DIAware {
private lateinit var _di: DI
override val di: DI get() = _di
// These will be initialized when _di is set
private val configService: ConfigService by instance()
private val startupTasks: List<StartupTask> by instance()
fun initialize() {
_di = DI {
bind<ConfigService>() with singleton { ConfigServiceImpl() }
bind<List<StartupTask>>() with provider {
listOf(DatabaseMigration(), CacheWarming())
}
}
// Now property delegates can resolve
configService.load()
startupTasks.forEach { it.execute() }
}
}Install with Tessl CLI
npx tessl i tessl/maven-org-kodein-di--kodein-di