KOtlin DEpendency INjection - A straightforward and yet very useful dependency retrieval container for Kotlin Multiplatform
—
Context-aware dependency management with support for hierarchical scopes, context translation, and lifecycle management for fine-grained control over dependency lifecycles and sharing.
Core context system for providing scoped dependency resolution with type-safe context management.
/**
* Context definition with type and value for scoped dependency resolution
* @param C Type of the context value
*/
interface DIContext<C : Any> {
/** TypeToken representing the context type */
val type: TypeToken<in C>
/** The actual context value */
val value: C
/**
* Immediate context implementation with direct value
* @param type TypeToken for the context type
* @param value The context value
*/
data class Value<C : Any>(
override val type: TypeToken<in C>,
override val value: C
) : DIContext<C>
/**
* Lazy context implementation with deferred value evaluation
* @param type TypeToken for the context type
* @param getValue Function that provides the context value when needed
*/
class Lazy<C : Any>(
override val type: TypeToken<in C>,
val getValue: () -> C
) : DIContext<C> {
override val value: C by lazy(getValue)
}
companion object {
/**
* Create a context with immediate value
* @param type TypeToken for the context type
* @param value The context value
* @return DIContext instance
*/
operator fun <C : Any> invoke(type: TypeToken<in C>, value: C): DIContext<C>
/**
* Create a context with lazy value evaluation
* @param type TypeToken for the context type
* @param getValue Function that provides the context value
* @return DIContext instance with lazy evaluation
*/
operator fun <C : Any> invoke(type: TypeToken<in C>, getValue: () -> C): DIContext<C>
}
}
/**
* Create a DIContext from a typed value
* @param context The context value
* @return DIContext wrapping the value
*/
fun <C : Any> diContext(context: C): DIContext<C>
/**
* Create a DIContext with lazy evaluation
* @param getContext Function that provides the context value
* @return DIContext with lazy evaluation
*/
fun <C : Any> diContext(getContext: () -> C): DIContext<C>Comprehensive scoping system for managing instance lifecycles and sharing patterns across different contexts.
/**
* Scope interface for managing instance lifecycles within contexts
* @param C Type of the context for this scope
*/
interface Scope<C> {
/**
* Get the registry for a specific context
* @param context The context value for this scope
* @return ScopeRegistry for managing instances in this context
*/
fun getRegistry(context: C): ScopeRegistry
}
/**
* Registry for storing and retrieving scoped instances
*/
interface ScopeRegistry {
/**
* Get or create an instance using the provided creator
* @param key Unique key for the instance
* @param sync Whether to synchronize access
* @param creator Function to create the instance if not found
* @return The stored or newly created instance
*/
fun <T> get(key: Any, sync: Boolean, creator: () -> T): T
/**
* Clear all instances from this registry
*/
fun clear()
}
/**
* Multi-item scope registry using concurrent map for thread safety
*/
class StandardScopeRegistry : ScopeRegistry {
// Implementation uses ConcurrentHashMap for thread-safe access
}
/**
* Single-item scope registry optimized for performance
*/
class SingleItemScopeRegistry : ScopeRegistry {
// Optimized for scopes that only hold one instance
}
/**
* Global scope not bound to any specific context
*/
class UnboundedScope : Scope<Any> {
// Provides global singleton behavior
}
/**
* Simple scope implementation without context dependency
*/
class NoScope : Scope<Any> {
// Basic scope without advanced features
}
/**
* Hierarchical scope with parent-child relationships
* @param C Child context type
* @param PC Parent context type
*/
class SubScope<C : Any, PC : Any>(
private val parentScope: Scope<PC>,
private val getParentContext: (C) -> PC
) : Scope<C>DSL methods for creating scoped binding contexts and managing scope hierarchies.
/**
* Base builder interface for scoped and context bindings
* @param C The context type for this builder
*/
interface DI.BindBuilder<C : Any> {
/** The context type for all bindings in this DSL context */
val contextType: TypeToken<C>
/** Whether the context is explicitly set */
val explicitContext: Boolean
/**
* Builder with scope support for scoped singletons and multitons
* @param C The scope's context type
*/
interface WithScope<C : Any> : BindBuilder<C> {
/** The scope for all bindings in this DSL context */
val scope: Scope<C>
}
}
/**
* Create a scoped binding builder
* @param scope The scope to use for bindings
* @return BindBuilder with scope support
*/
fun <C : Any> DI.Builder.scoped(scope: Scope<C>): DI.BindBuilder.WithScope<C>
/**
* Create a context-aware binding builder
* @return BindBuilder for the specified context type
*/
inline fun <reified C : Any> DI.Builder.contexted(): DI.BindBuilder<C>Advanced context translation system for converting between different context types and scoped dependency resolution.
/**
* Interface for translating between different context types
* @param C Source context type
* @param S Target context type
*/
interface ContextTranslator<C, S> {
/**
* Translate a context for dependency resolution
* @param key The binding key being resolved
* @param context The source context
* @return Translated context or null if translation not possible
*/
fun translate(key: DI.Key<*, *, *>, context: C): S?
}
/**
* Create a context translator using a transformation function
* @param t Function that performs the context translation
* @return ContextTranslator instance
*/
fun <C, S> contextTranslator(t: DirectDI.(C) -> S?): ContextTranslator<C, S>
/**
* Create a context finder that locates context from DirectDI
* @param t Function that finds the context
* @return ContextTranslator that uses the finder function
*/
fun <S> contextFinder(t: DirectDI.() -> S): ContextTranslator<Any, S>
/**
* Register a context translator with the DI builder
* @param translator The context translator to register
*/
fun <C, S> DI.Builder.registerContextTranslator(translator: ContextTranslator<C, S>)
/**
* Register a context finder with the DI builder
* @param t Function that finds the context
*/
fun <S> DI.Builder.registerContextFinder(t: DirectDI.() -> S)Platform-specific scoping implementations optimized for JVM environments with advanced memory management.
/**
* Scope using weak references for automatic cleanup (JVM only)
* @param C Context type for the weak scope
*/
class WeakContextScope<C : Any> : Scope<C> {
// Uses WeakHashMap for automatic context cleanup
// when contexts are garbage collected
}
/**
* Reference makers for different memory management strategies (JVM only)
*/
// Thread-local references for thread-scoped singletons
val threadLocal: RefMaker
// Soft references for memory-sensitive caching
val softReference: RefMaker
// Weak references for automatic cleanup
val weakReference: RefMakerAdvanced context manipulation and scoped dependency access patterns for fine-grained dependency control.
/**
* Create a new DIAware with different context
* @param context New context for scoped dependencies
* @param trigger Optional trigger for dependency resolution
* @return New DIAware instance with updated context
*/
fun DIAware.On(context: DIContext<*> = this.diContext, trigger: DITrigger? = this.diTrigger): DI
/**
* Create a new DIAware with typed context
* @param context Context value
* @param trigger Optional trigger for dependency resolution
* @return New DIAware instance with typed context
*/
fun <C : Any> DIAware.on(context: C, trigger: DITrigger? = this.diTrigger): DI
/**
* Create a new DirectDI with different context
* @param context Context value for scoped dependencies
* @return DirectDI instance with the specified context
*/
fun <C : Any> DirectDIAware.on(context: C): DirectDIUsage Examples:
// Define context types
data class UserSession(val userId: String, val sessionId: String)
data class RequestContext(val requestId: String, val clientId: String)
// Create scoped DI with multiple contexts
val di = DI {
// Global singleton (no scope)
bind<ConfigService>() with singleton { ConfigServiceImpl() }
// Request-scoped dependencies
scoped(UnboundedScope).apply {
bind<RequestLogger>() with singleton { RequestLoggerImpl() }
}
// User session scoped dependencies
contexted<UserSession>().apply {
bind<UserPreferences>() with singleton {
UserPreferencesImpl(diContext.value.userId)
}
bind<UserCache>() with singleton {
UserCacheImpl(diContext.value.sessionId)
}
}
// JVM-specific: Thread-local scope for per-thread singletons
bind<Database>() with singleton(ref = threadLocal) {
DatabaseConnection()
}
// Context translator for automatic context conversion
registerContextTranslator<RequestContext, UserSession> { requestCtx ->
// Convert request context to user session context
val userId = userService.getUserId(requestCtx.clientId)
UserSession(userId, generateSessionId())
}
}
// Using scoped dependencies
class RequestHandler : DIAware {
override val di = di
fun handleRequest(request: HttpRequest) {
val requestContext = RequestContext(request.id, request.clientId)
// Switch to request context
val requestDI = on(requestContext)
// These use request-scoped context
val logger: RequestLogger by requestDI.instance()
val userPrefs: UserPreferences by requestDI.instance() // Auto-translated from RequestContext
val userCache: UserCache by requestDI.instance()
logger.info("Processing request ${request.id}")
val preferences = userPrefs.load()
userCache.store("last_request", request.timestamp)
}
}
// Custom scope implementation
class TimedScope(private val ttlMs: Long) : Scope<Any> {
private val registries = mutableMapOf<Any, TimedScopeRegistry>()
override fun getRegistry(context: Any): ScopeRegistry {
return registries.getOrPut(context) { TimedScopeRegistry(ttlMs) }
}
private class TimedScopeRegistry(private val ttlMs: Long) : ScopeRegistry {
private data class TimedEntry<T>(val value: T, val timestamp: Long)
private val cache = mutableMapOf<Any, TimedEntry<*>>()
override fun <T> get(key: Any, sync: Boolean, creator: () -> T): T {
val now = System.currentTimeMillis()
val entry = cache[key] as? TimedEntry<T>
return if (entry != null && (now - entry.timestamp) < ttlMs) {
entry.value
} else {
val newValue = creator()
cache[key] = TimedEntry(newValue, now)
newValue
}
}
override fun clear() = cache.clear()
}
}
// Using custom scopes
val timedDI = DI {
val fiveMinuteScope = TimedScope(5 * 60 * 1000) // 5 minutes TTL
scoped(fiveMinuteScope).apply {
bind<ExpensiveService>() with singleton {
ExpensiveServiceImpl().also {
it.loadData() // Expensive initialization
}
}
}
}
// Hierarchical scoping
class ApplicationService : DIAware {
override val di = DI {
// Application-level scope
bind<AppConfig>() with singleton { AppConfigImpl() }
// Module-level scope (child of application scope)
val moduleScope = SubScope(UnboundedScope) { _: ModuleContext -> Unit }
scoped(moduleScope).apply {
bind<ModuleService>() with singleton { ModuleServiceImpl(instance()) }
}
// Request-level scope (child of module scope)
contexted<RequestContext>().apply {
bind<RequestProcessor>() with singleton {
RequestProcessorImpl(instance(), instance()) // Gets from parent scopes
}
}
}
private val appConfig: AppConfig by instance() // Application scope
fun processRequest(requestContext: RequestContext) {
val requestDI = on(requestContext)
val processor: RequestProcessor by requestDI.instance() // Request scope
processor.process()
}
}
// JVM-specific weak reference scoping
val jvmDI = DI {
// Weak context scope - automatically cleans up when contexts are GC'd
val weakScope = WeakContextScope<UserSession>()
scoped(weakScope).apply {
bind<UserData>() with singleton {
UserDataImpl(diContext.value.userId)
}
}
// Different reference types for memory management
bind<ImageCache>() with singleton(ref = softReference) {
ImageCacheImpl() // Uses soft references, cleared under memory pressure
}
bind<ThreadContext>() with singleton(ref = threadLocal) {
ThreadContextImpl() // One per thread
}
bind<TemporaryData>() with singleton(ref = weakReference) {
TemporaryDataImpl() // Automatically cleaned up
}
}
// Context translation for complex scenarios
val translatingDI = DI {
// Register multiple context translators
registerContextTranslator<HttpRequest, UserSession> { request ->
val session = sessionService.getSession(request.sessionToken)
UserSession(session.userId, session.id)
}
registerContextTranslator<UserSession, UserPermissions> { session ->
permissionService.getPermissions(session.userId)
}
registerContextFinder<DatabaseConnection> {
connectionPool.getConnection()
}
// Bindings that use translated contexts
contexted<UserSession>().apply {
bind<UserService>() with singleton { UserServiceImpl(diContext.value.userId) }
}
contexted<UserPermissions>().apply {
bind<AuthorizationService>() with singleton {
AuthorizationServiceImpl(diContext.value)
}
}
}Install with Tessl CLI
npx tessl i tessl/maven-org-kodein-di--kodein-di