CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-kodein-di--kodein-di

KOtlin DEpendency INjection - A straightforward and yet very useful dependency retrieval container for Kotlin Multiplatform

Pending
Overview
Eval results
Files

lazy-property-delegation.mddocs/

Lazy Property Delegation

Property delegation pattern for lazy dependency retrieval using the DIAware interface with automatic initialization, caching, and optional triggers for controlling when dependencies are resolved.

Capabilities

DIAware Interface

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()
}

Instance Property Delegates

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?>

Provider Property Delegates

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)?>

Factory Property Delegates

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)?>

Constant Property Delegates

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?>

Named Property Access

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 parameters

Context and Trigger Management

Advanced 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>

Property Delegate Implementation

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

docs

advanced-features.md

binding-dsl.md

container-configuration.md

direct-access.md

index.md

lazy-property-delegation.md

scoping-and-context.md

tile.json