KOtlin DEpendency INjection - A straightforward and yet very useful dependency retrieval container for Kotlin Multiplatform
—
Immediate dependency retrieval without property delegation for cases requiring direct access to dependencies, providing synchronous resolution and explicit control over dependency lifecycle.
Core interface for immediate dependency access without lazy initialization or property delegation overhead.
/**
* Base interface for classes that use direct dependency injection
*/
interface DirectDIAware {
/** Reference to the DirectDI instance */
val directDI: DirectDI
}
/**
* Base functionality for direct dependency access
*/
interface DirectDIBase : DirectDIAware {
/** The underlying container for dependency resolution */
val container: DIContainer
/** Get a regular lazy DI instance from this DirectDI */
val lazy: DI
/** Alias for lazy property */
val di: DI
/**
* Create a new DirectDI with different context
* @param context New context for scoped dependency resolution
* @return DirectDI instance with the specified context
*/
fun On(context: DIContext<*>): DirectDI
}
/**
* Platform-specific DirectDI interface for immediate dependency access
* Acts like a DI object but returns values instead of property delegates
*/
expect interface DirectDI : DirectDIBaseImmediate retrieval of dependency instances without lazy initialization or caching overhead.
/**
* Get an instance of type T immediately
* @param type TypeToken representing the type to retrieve
* @param tag Optional tag for disambiguation
* @return Instance of type T
* @throws DI.NotFoundException if no binding is found
* @throws DI.DependencyLoopException if circular dependency is detected
*/
fun <T : Any> DirectDI.Instance(type: TypeToken<T>, tag: Any? = null): T
/**
* Get an instance with factory argument immediately
* @param argType TypeToken for the argument type
* @param type TypeToken for the return type
* @param tag Optional tag for disambiguation
* @param arg Argument value for the factory
* @return Instance of type T created with the argument
* @throws DI.NotFoundException if no binding is found
* @throws DI.DependencyLoopException if circular dependency is detected
*/
fun <A, T : Any> DirectDI.Instance(
argType: TypeToken<in A>,
type: TypeToken<T>,
tag: Any? = null,
arg: A
): T
/**
* Get an instance or null if not found
* @param type TypeToken representing the type to retrieve
* @param tag Optional tag for disambiguation
* @return Instance of type T or null if no binding found
* @throws DI.DependencyLoopException if circular dependency is detected
*/
fun <T : Any> DirectDI.InstanceOrNull(type: TypeToken<T>, tag: Any? = null): T?
/**
* Get an instance with factory argument or null if not found
* @param argType TypeToken for the argument type
* @param type TypeToken for the return type
* @param tag Optional tag for disambiguation
* @param arg Argument value for the factory
* @return Instance of type T or null if no binding found
* @throws DI.DependencyLoopException if circular dependency is detected
*/
fun <A, T : Any> DirectDI.InstanceOrNull(
argType: TypeToken<in A>,
type: TypeToken<T>,
tag: Any? = null,
arg: A
): T?Immediate retrieval of provider functions that create new instances when called.
/**
* Get a provider function immediately
* @param type TypeToken representing the type to provide
* @param tag Optional tag for disambiguation
* @return Provider function () -> T
* @throws DI.NotFoundException if no binding is found
* @throws DI.DependencyLoopException if circular dependency is detected
*/
fun <T : Any> DirectDI.Provider(type: TypeToken<T>, tag: Any? = null): () -> T
/**
* Get a provider curried from a factory with 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 currying
* @return Provider function () -> T
* @throws DI.NotFoundException if no binding is found
* @throws DI.DependencyLoopException if circular dependency is detected
*/
fun <A, T : Any> DirectDI.Provider(
argType: TypeToken<in A>,
type: TypeToken<T>,
tag: Any? = null,
arg: () -> A
): () -> T
/**
* Get a provider function or null if not found
* @param type TypeToken representing the type to provide
* @param tag Optional tag for disambiguation
* @return Provider function (() -> T)? or null if no binding found
* @throws DI.DependencyLoopException if circular dependency is detected
*/
fun <T : Any> DirectDI.ProviderOrNull(type: TypeToken<T>, tag: Any? = null): (() -> T)?
/**
* Get a provider curried from a factory or null if not found
* @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 Provider function (() -> T)? or null if no binding found
* @throws DI.DependencyLoopException if circular dependency is detected
*/
fun <A, T : Any> DirectDI.ProviderOrNull(
argType: TypeToken<in A>,
type: TypeToken<T>,
tag: Any? = null,
arg: () -> A
): (() -> T)?Immediate retrieval of factory functions that accept arguments and create instances.
/**
* Get a factory function immediately
* @param argType TypeToken for the argument type
* @param type TypeToken for the return type
* @param tag Optional tag for disambiguation
* @return Factory function (A) -> T
* @throws DI.NotFoundException if no binding is found
* @throws DI.DependencyLoopException if circular dependency is detected
*/
fun <A, T : Any> DirectDI.Factory(
argType: TypeToken<in A>,
type: TypeToken<T>,
tag: Any? = null
): (A) -> T
/**
* Get a factory function or null if not found
* @param argType TypeToken for the argument type
* @param type TypeToken for the return type
* @param tag Optional tag for disambiguation
* @return Factory function ((A) -> T)? or null if no binding found
* @throws DI.DependencyLoopException if circular dependency is detected
*/
fun <A, T : Any> DirectDI.FactoryOrNull(
argType: TypeToken<in A>,
type: TypeToken<T>,
tag: Any? = null
): ((A) -> T)?Type-safe extension methods for DirectDIAware that eliminate the need for explicit TypeToken parameters by using reified generics.
/**
* Get a factory function for type T with argument type A
* @param tag Optional tag for disambiguation
* @return Factory function that takes A and returns T
* @throws DI.NotFoundException if no binding is found
*/
inline fun <reified A : Any, reified T : Any> DirectDIAware.factory(tag: Any? = null): (A) -> T
/**
* Get a factory function for type T with argument type A, returning null if not found
* @param tag Optional tag for disambiguation
* @return Factory function or null if no binding is found
*/
inline fun <reified A : Any, reified T : Any> DirectDIAware.factoryOrNull(tag: Any? = null): ((A) -> T)?
/**
* Get a provider function for type T
* @param tag Optional tag for disambiguation
* @return Provider function that returns T
* @throws DI.NotFoundException if no binding is found
*/
inline fun <reified T : Any> DirectDIAware.provider(tag: Any? = null): () -> T
/**
* Get a provider function for type T with curried argument
* @param tag Optional tag for disambiguation
* @param arg Argument value to curry into the provider
* @return Provider function that returns T
*/
inline fun <reified A : Any, reified T : Any> DirectDIAware.provider(tag: Any? = null, arg: A): () -> T
/**
* Get a provider function for type T with typed argument
* @param tag Optional tag for disambiguation
* @param arg Typed argument value to curry into the provider
* @return Provider function that returns T
*/
inline fun <A, reified T : Any> DirectDIAware.provider(tag: Any? = null, arg: Typed<A>): () -> T
/**
* Get a provider function for type T with lazy argument
* @param tag Optional tag for disambiguation
* @param fArg Function that provides the argument value
* @return Provider function that returns T
*/
inline fun <reified A : Any, reified T : Any> DirectDIAware.provider(tag: Any? = null, noinline fArg: () -> A): () -> T
/**
* Get a provider function for type T, returning null if not found
* @param tag Optional tag for disambiguation
* @return Provider function or null if no binding is found
*/
inline fun <reified T : Any> DirectDIAware.providerOrNull(tag: Any? = null): (() -> T)?
/**
* Get a provider function for type T with curried argument, returning null if not found
* @param tag Optional tag for disambiguation
* @param arg Argument value to curry into the provider
* @return Provider function or null if no binding is found
*/
inline fun <reified A : Any, reified T : Any> DirectDIAware.providerOrNull(tag: Any? = null, arg: A): (() -> T)?
/**
* Get a provider function for type T with typed argument, returning null if not found
* @param tag Optional tag for disambiguation
* @param arg Typed argument value to curry into the provider
* @return Provider function or null if no binding is found
*/
inline fun <A, reified T : Any> DirectDIAware.providerOrNull(tag: Any? = null, arg: Typed<A>): (() -> T)?
/**
* Get a provider function for type T with lazy argument, returning null if not found
* @param tag Optional tag for disambiguation
* @param fArg Function that provides the argument value
* @return Provider function or null if no binding is found
*/
inline fun <reified A : Any, reified T : Any> DirectDIAware.providerOrNull(tag: Any? = null, noinline fArg: () -> A): (() -> T)?
/**
* Get an instance of type T immediately
* @param tag Optional tag for disambiguation
* @return Instance of type T
* @throws DI.NotFoundException if no binding is found
*/
inline fun <reified T : Any> DirectDIAware.instance(tag: Any? = null): T
/**
* Get an instance of type T with argument
* @param tag Optional tag for disambiguation
* @param arg Argument value for factory-based bindings
* @return Instance of type T
*/
inline fun <reified A : Any, reified T : Any> DirectDIAware.instance(tag: Any? = null, arg: A): T
/**
* Get an instance of type T with typed argument
* @param tag Optional tag for disambiguation
* @param arg Typed argument value for factory-based bindings
* @return Instance of type T
*/
inline fun <A, reified T : Any> DirectDIAware.instance(tag: Any? = null, arg: Typed<A>): T
/**
* Get an instance of type T, returning null if not found
* @param tag Optional tag for disambiguation
* @return Instance of type T or null if no binding is found
*/
inline fun <reified T : Any> DirectDIAware.instanceOrNull(tag: Any? = null): T?
/**
* Get an instance of type T with argument, returning null if not found
* @param tag Optional tag for disambiguation
* @param arg Argument value for factory-based bindings
* @return Instance of type T or null if no binding is found
*/
inline fun <reified A : Any, reified T : Any> DirectDIAware.instanceOrNull(tag: Any? = null, arg: A): T?
/**
* Get an instance of type T with typed argument, returning null if not found
* @param tag Optional tag for disambiguation
* @param arg Typed argument value for factory-based bindings
* @return Instance of type T or null if no binding is found
*/
inline fun <A, reified T : Any> DirectDIAware.instanceOrNull(tag: Any? = null, arg: Typed<A>): T?
/**
* Create a DirectDI with typed context
* @param context Context value for scoped dependencies
* @return DirectDI instance with the specified context
*/
inline fun <reified C : Any> DirectDIAware.on(context: C): DirectDIAdvanced dependency injection for constructor parameters with automatic parameter resolution.
/**
* Create a new instance using constructor injection (no parameters)
* @param constructor Constructor function reference
* @return New instance with injected dependencies
*/
fun <T> DirectDIAware.new(constructor: () -> T): T
/**
* Create a new instance using constructor injection (1 parameter)
* @param constructor Constructor function reference
* @return New instance with injected dependencies
*/
fun <P1, T> DirectDIAware.new(constructor: (P1) -> T): T
/**
* Create a new instance using constructor injection (2 parameters)
* @param constructor Constructor function reference
* @return New instance with injected dependencies
*/
fun <P1, P2, T> DirectDIAware.new(constructor: (P1, P2) -> T): T
// ... up to 22 parameter overloads
/**
* Create a new instance within a DirectDI context
* @param creator Function that creates the instance using DirectDI
* @return Created instance
*/
inline fun <T> DirectDIAware.newInstance(creator: DirectDI.() -> T): TDirect context switching and scoped dependency access for fine-grained control over dependency resolution contexts.
/**
* Create a DirectDI with typed context
* @param context Context value for scoped dependencies
* @return DirectDI instance with the specified context
*/
fun <C : Any> DirectDIAware.on(context: C): DirectDI
/**
* Access the lazy DI interface from DirectDIAware
*/
val DirectDIAware.lazy: DIPlatform-specific DirectDI implementations with optimized method signatures and additional multi-binding support on JVM platform.
/**
* JVM-specific DirectDI methods for multi-binding support
* These methods are only available on the JVM platform
*/
/**
* Gets all factories that can return a T for the given argument type, return type and tag
* @param argType TypeToken for the argument type
* @param type TypeToken for the return type
* @param tag Optional tag for disambiguation
* @return List of all matching factory functions
*/
fun <A, T : Any> DirectDI.AllFactories(argType: TypeToken<in A>, type: TypeToken<T>, tag: Any? = null): List<(A) -> T>
/**
* Gets all providers that can return a T for the given type and tag
* @param type TypeToken for the return type
* @param tag Optional tag for disambiguation
* @return List of all matching provider functions
*/
fun <T : Any> DirectDI.AllProviders(type: TypeToken<T>, tag: Any? = null): List<() -> T>
/**
* Gets all providers that can return a T for the given type and tag, curried from factories
* @param argType TypeToken for the factory argument type
* @param type TypeToken for the return type
* @param tag Optional tag for disambiguation
* @param arg Function providing the argument value
* @return List of all matching provider functions with curried arguments
*/
fun <A, T : Any> DirectDI.AllProviders(argType: TypeToken<in A>, type: TypeToken<T>, tag: Any? = null, arg: () -> A): List<() -> T>
/**
* Gets all instances that can return a T for the given type and tag
* @param type TypeToken for the return type
* @param tag Optional tag for disambiguation
* @return List of all matching instances
*/
fun <T : Any> DirectDI.AllInstances(type: TypeToken<T>, tag: Any? = null): List<T>
/**
* Gets all instances that can return a T for the given type and tag, from factories
* @param argType TypeToken for the factory argument type
* @param type TypeToken for the return type
* @param tag Optional tag for disambiguation
* @param arg Argument value for the factories
* @return List of all matching instances created with the argument
*/
fun <A, T : Any> DirectDI.AllInstances(argType: TypeToken<in A>, type: TypeToken<T>, tag: Any? = null, arg: A): List<T>
/**
* Reified convenience methods for JVM multi-binding support
*/
inline fun <reified A : Any, reified T : Any> DirectDI.allFactories(tag: Any? = null): List<(A) -> T>
inline fun <reified T : Any> DirectDI.allProviders(tag: Any? = null): List<() -> T>
inline fun <reified A : Any, reified T : Any> DirectDI.allProviders(tag: Any? = null, noinline arg: () -> A): List<() -> T>
inline fun <reified T : Any> DirectDI.allInstances(tag: Any? = null): List<T>
inline fun <reified A : Any, reified T : Any> DirectDI.allInstances(tag: Any? = null, arg: A): List<T>
// and integration with Java reflection
}
// JavaScript-specific DirectDI implementation
// Located in src/jsBasedMain/kotlin/org/kodein/di/DirectDIJS.kt
actual interface DirectDI : DirectDIBase {
// JS-optimized implementations for browser and Node.js environments
}Usage Examples:
// Basic DirectDI usage
class UserService(private val directDI: DirectDI) : DirectDIAware {
override val directDI = directDI
fun createUser(userData: UserData): User {
// Direct instance retrieval - immediate access
val repository = directDI.instance<UserRepository>()
val validator = directDI.instance<UserValidator>()
val emailService = directDI.instance<EmailService>("smtp")
// Factory with argument - immediate factory call
val logger = directDI.factory<String, Logger>()(this::class.simpleName!!)
// Provider for new instances each time
val sessionProvider = directDI.provider<UserSession>()
val user = User(userData)
repository.save(user)
emailService.sendWelcome(user)
logger.info("User created: ${user.id}")
return user
}
fun processUsers(userIds: List<String>) {
// Get factory once, use multiple times
val userFactory = directDI.factory<String, UserProcessor>()
userIds.forEach { userId ->
val processor = userFactory(userId) // Create processor for each user
processor.process()
}
}
}
// Constructor injection with new operator
class OrderService : DirectDIAware {
override val directDI: DirectDI = DI.direct {
bind<PaymentProcessor>() with singleton { StripePaymentProcessor() }
bind<InventoryService>() with singleton { InventoryServiceImpl() }
bind<EmailService>() with provider { EmailServiceImpl() }
bind<Logger>() with factory { name: String -> LoggerFactory.create(name) }
}
fun processOrder(orderData: OrderData) {
// Automatic constructor injection - finds dependencies automatically
val orderValidator = new(::OrderValidator) // Injects whatever OrderValidator needs
val paymentHandler = new(::PaymentHandler) // Injects PaymentProcessor, Logger, etc.
val fulfillmentService = new(::FulfillmentService) // Injects InventoryService, EmailService
orderValidator.validate(orderData)
val payment = paymentHandler.process(orderData.payment)
fulfillmentService.fulfill(orderData, payment)
}
}
// Context switching for scoped dependencies
class RequestHandler : DirectDIAware {
override val directDI: DirectDI = appDirectDI
fun handleRequest(request: HttpRequest) {
val requestContext = RequestContext(request.sessionId, request.userId)
// Switch to request-scoped context
val scopedDI = directDI.on(requestContext)
// These will use the request context for scoped singletons
val userSession = scopedDI.instance<UserSession>() // Scoped to this request
val requestLogger = scopedDI.instance<Logger>("request") // Request-specific logger
val permissions = scopedDI.instance<UserPermissions>() // Cached per request
// Process request with scoped dependencies
processWithContext(userSession, requestLogger, permissions, request)
}
}
// Mixing direct and lazy access patterns
class HybridService : DIAware, DirectDIAware {
override val di: DI = appDI
override val directDI: DirectDI = appDI.direct
// Some dependencies as lazy properties (rarely accessed)
private val configService: ConfigService by instance()
private val metricsCollector: MetricsCollector by instance()
fun processData(data: List<DataItem>) {
// Direct access for per-operation dependencies
val processor = directDI.instance<DataProcessor>()
val validator = directDI.instance<DataValidator>()
// Lazy properties accessed only when needed
val config = configService.getProcessingConfig()
data.forEach { item ->
// New processor instance for each item if needed
val itemProcessor = directDI.provider<ItemProcessor>()()
itemProcessor.process(item, config)
// Record metrics (lazy property)
metricsCollector.record("item_processed", item.type)
}
}
}
// Error handling with direct access
class RobustService : DirectDIAware {
override val directDI: DirectDI = serviceDirectDI
fun robustOperation() {
try {
val primaryService = directDI.instance<PrimaryService>()
primaryService.execute()
} catch (e: DI.NotFoundException) {
// Fallback to secondary service
val fallbackService = directDI.instanceOrNull<FallbackService>()
if (fallbackService != null) {
fallbackService.execute()
} else {
throw ServiceUnavailableException("No service available", e)
}
} catch (e: DI.DependencyLoopException) {
val emergencyService = new(::EmergencyService) // Bypass DI for emergency
emergencyService.handleEmergency()
}
}
}Install with Tessl CLI
npx tessl i tessl/maven-org-kodein-di--kodein-di