CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-insert-koin--koin-core-iossimulatorarm64

Koin Core is a pragmatic lightweight dependency injection framework for Kotlin Multiplatform with iOS Simulator ARM64 target support.

Pending
Overview
Eval results
Files

qualifiers-parameters.mddocs/

Qualifiers & Parameters

This document covers how to distinguish between similar dependencies using qualifiers and how to pass runtime parameters during dependency resolution.

Overview

Koin provides two key mechanisms for handling complex dependency scenarios:

  • Qualifiers - Distinguish between different instances of the same type
  • Parameters - Pass runtime values during dependency resolution

These features enable sophisticated dependency injection patterns while maintaining type safety and clarity.

Qualifier System

Qualifiers help distinguish between multiple definitions of the same type, enabling you to have different implementations or configurations for different contexts.

Qualifier Interface

interface Qualifier {
    val value: QualifierValue
}

typealias QualifierValue = String

Qualifier Creation Functions

// String-based qualifiers
fun named(name: String): StringQualifier
fun qualifier(name: String): StringQualifier
fun _q(name: String): StringQualifier

// Enum-based qualifiers
fun <E : Enum<E>> named(enum: Enum<E>): Qualifier
fun <E : Enum<E>> qualifier(enum: Enum<E>): Qualifier
val <E : Enum<E>> Enum<E>.qualifier: Qualifier

// Type-based qualifiers
inline fun <reified T> named(): TypeQualifier
inline fun <reified T> qualifier(): TypeQualifier
inline fun <reified T> _q(): TypeQualifier

Qualifier Implementations

data class StringQualifier(override val value: QualifierValue) : Qualifier

class TypeQualifier(val type: KClass<*>) : Qualifier {
    override val value: QualifierValue
}

Using String Qualifiers

Basic String Qualifiers

import org.koin.core.qualifier.named
import org.koin.dsl.*

val networkModule = module {
    // Different HTTP clients for different purposes
    single<HttpClient>(named("public")) { 
        HttpClient().apply { 
            timeout = 30000
        }
    }
    
    single<HttpClient>(named("authenticated")) { 
        HttpClient().apply {
            timeout = 60000
            addAuthInterceptor()
        }
    }
    
    single<HttpClient>(named("admin")) {
        HttpClient().apply {
            timeout = 120000
            addAuthInterceptor()
            addAdminHeaders()
        }
    }
}

// Resolution with qualifiers
val koin = koinApplication { modules(networkModule) }.koin

val publicClient: HttpClient = koin.get(named("public"))
val authClient: HttpClient = koin.get(named("authenticated"))
val adminClient: HttpClient = koin.get(named("admin"))

Environment-Based Qualifiers

val configModule = module {
    single<DatabaseConfig>(named("development")) {
        DatabaseConfig("jdbc:h2:mem:dev", "dev_user", "dev_pass")
    }
    
    single<DatabaseConfig>(named("production")) {
        DatabaseConfig("jdbc:postgresql://prod-server/db", "prod_user", "prod_pass")
    }
    
    single<DatabaseConfig>(named("test")) {
        DatabaseConfig("jdbc:h2:mem:test", "test_user", "test_pass")
    }
}

class DatabaseService : KoinComponent {
    private val environment = System.getenv("ENVIRONMENT") ?: "development"
    private val config: DatabaseConfig = get(named(environment))
}

Service Variant Qualifiers

val serviceModule = module {
    // Different implementations of the same service
    single<PaymentService>(named("stripe")) { StripePaymentService() }
    single<PaymentService>(named("paypal")) { PayPalPaymentService() }
    single<PaymentService>(named("mock")) { MockPaymentService() }
    
    // Email service variants
    single<EmailService>(named("smtp")) { SMTPEmailService() }
    single<EmailService>(named("sendgrid")) { SendGridEmailService() }
    single<EmailService>(named("console")) { ConsoleEmailService() }
}

Using Enum Qualifiers

Enum-Based Qualifiers

enum class ServiceTier {
    FREE, PREMIUM, ENTERPRISE
}

enum class Region {
    US_EAST, US_WEST, EUROPE, ASIA
}

val tierModule = module {
    // Using enum qualifiers
    single<FeatureSet>(ServiceTier.FREE.qualifier) {
        FreeFeatureSet()
    }
    
    single<FeatureSet>(ServiceTier.PREMIUM.qualifier) {
        PremiumFeatureSet()
    }
    
    single<FeatureSet>(ServiceTier.ENTERPRISE.qualifier) {
        EnterpriseFeatureSet()
    }
    
    // Regional services
    single<RegionalService>(Region.US_EAST.qualifier) {
        USEastService()
    }
    
    single<RegionalService>(Region.EUROPE.qualifier) {
        EuropeService()
    }
}

// Usage with enum qualifiers
class TieredService : KoinComponent {
    fun getFeatureSet(tier: ServiceTier): FeatureSet {
        return get(tier.qualifier)
    }
    
    fun getRegionalService(region: Region): RegionalService {
        return get(named(region))  // Alternative syntax
    }
}

Using Type Qualifiers

Type-Based Qualifiers

// Marker types for different contexts
class ProductionContext
class DevelopmentContext
class TestContext

val contextModule = module {
    // Type-based qualifiers
    single<Logger>(named<ProductionContext>()) {
        FileLogger("/var/log/app.log")
    }
    
    single<Logger>(named<DevelopmentContext>()) {
        ConsoleLogger()
    }
    
    single<Logger>(named<TestContext>()) {
        MockLogger()
    }
    
    // Cache implementations by type
    single<Cache>(qualifier<UserCache>()) {
        RedisCache("user-cache")
    }
    
    single<Cache>(qualifier<SessionCache>()) {
        InMemoryCache()
    }
}

// Usage
class ContextAwareService : KoinComponent {
    private val prodLogger: Logger = get(named<ProductionContext>())
    private val userCache: Cache = get(qualifier<UserCache>())
}

Generic Type Qualifiers

// Different cache instances for different data types
val cacheModule = module {
    single<Cache<User>>(named("user")) { 
        InMemoryCache<User>() 
    }
    
    single<Cache<Product>>(named("product")) { 
        RedisCache<Product>("products") 
    }
    
    single<Cache<String>>(named("session")) { 
        InMemoryCache<String>() 
    }
}

Parameter System

Parameters allow you to pass runtime values during dependency resolution, enabling dynamic configuration and context-specific instantiation.

ParametersHolder Class

class ParametersHolder(
    internal val _values: MutableList<Any?> = mutableListOf(),
    val useIndexedValues: Boolean? = null
) {
    val values: List<Any?>
    var index: Int
    
    // Access by index
    operator fun <T> get(i: Int): T
    fun <T> set(i: Int, t: T)
    
    // Access by type
    inline fun <reified T : Any> get(): T
    inline fun <reified T : Any> getOrNull(): T?
    fun <T> getOrNull(clazz: KClass<*>): T?
    fun <T> elementAt(i: Int, clazz: KClass<*>): T
    
    // Destructuring components
    inline operator fun <reified T> component1(): T
    inline operator fun <reified T> component2(): T
    inline operator fun <reified T> component3(): T
    inline operator fun <reified T> component4(): T
    inline operator fun <reified T> component5(): T
    
    // Utilities
    fun size(): Int
    fun isEmpty(): Boolean
    fun isNotEmpty(): Boolean
    fun add(value: Any): ParametersHolder
    fun insert(index: Int, value: Any): ParametersHolder
}

typealias ParametersDefinition = () -> ParametersHolder

Parameter Creation Functions

// Standard parameters - indexed or type-based resolution
fun parametersOf(vararg parameters: Any?): ParametersHolder

// Array-based parameters - consumed one by one (indexed)
fun parameterArrayOf(vararg parameters: Any?): ParametersHolder

// Set-based parameters - different types of values
fun parameterSetOf(vararg parameters: Any?): ParametersHolder

// Empty parameters
fun emptyParametersHolder(): ParametersHolder

Working with Parameters

Basic Parameter Usage

val parameterModule = module {
    factory<DatabaseConnection> { params ->
        val host: String = params.get()
        val port: Int = params.get()
        val database: String = params.get()
        DatabaseConnection(host, port, database)
    }
}

val koin = koinApplication { modules(parameterModule) }.koin

// Pass parameters during resolution
val connection: DatabaseConnection = koin.get {
    parametersOf("localhost", 5432, "myapp")
}

Indexed Parameter Access

val indexedModule = module {
    factory<ApiClient> { params ->
        val baseUrl: String = params[0]        // Get by index
        val timeout: Int = params[1]
        val apiKey: String = params[2]
        ApiClient(baseUrl, timeout, apiKey)
    }
}

// Usage
val apiClient: ApiClient = koin.get {
    parametersOf("https://api.example.com", 30000, "secret-key")
}

Type-Based Parameter Access

val typedModule = module {
    factory<EmailService> { params ->
        val config: EmailConfig = params.get()     // Get by type
        val logger: Logger = params.get()
        val retries: Int = params.get()
        EmailService(config, logger, retries)
    }
}

// Usage with different types
val emailService: EmailService = koin.get {
    parametersOf(
        EmailConfig("smtp.example.com"),
        FileLogger("/tmp/email.log"),
        3
    )
}

Destructuring Parameters

val destructuringModule = module {
    factory<ServiceConfig> { params ->
        val (host: String, port: Int, ssl: Boolean) = params
        ServiceConfig(host, port, ssl)
    }
}

// Usage
val config: ServiceConfig = koin.get {
    parametersOf("api.example.com", 443, true)
}

Parameter Access Modes

val parameterModule = module {
    factory<MultiTypeService> { params ->
        // Mixed parameter resolution - by index or by type
        val value1: String = params.get()  // By type
        val value2: Int = params.get()     // By type
        MultiTypeService(value1, value2.toString())
    }
}

// Regular parameters
val service: MultiTypeService = koin.get {
    parametersOf("value", 42)
}

Combining Qualifiers and Parameters

Qualified Parameterized Definitions

val combinedModule = module {
    // Different database connections with parameters
    factory<DatabaseConnection>(named("primary")) { params ->
        val credentials: Credentials = params.get()
        PrimaryDatabaseConnection(credentials)
    }
    
    factory<DatabaseConnection>(named("backup")) { params ->
        val credentials: Credentials = params.get()
        val timeout: Int = params.get()
        BackupDatabaseConnection(credentials, timeout)
    }
    
    factory<DatabaseConnection>(named("readonly")) { params ->
        val (host: String, database: String) = params
        ReadOnlyDatabaseConnection(host, database)
    }
}

// Usage with both qualifiers and parameters
val primary: DatabaseConnection = koin.get(named("primary")) {
    parametersOf(Credentials("user", "pass"))
}

val backup: DatabaseConnection = koin.get(named("backup")) {
    parametersOf(Credentials("backup_user", "backup_pass"), 60000)
}

val readonly: DatabaseConnection = koin.get(named("readonly")) {
    parametersOf("replica.db.com", "myapp_readonly")
}

Dynamic Qualifier Selection

val dynamicModule = module {
    single<CacheService>(named("redis")) { params ->
        val config: RedisConfig = params.get()
        RedisCacheService(config)
    }
    
    single<CacheService>(named("memory")) { params ->
        val maxSize: Int = params.getOrNull() ?: 1000
        InMemoryCacheService(maxSize)
    }
}

class CacheManager : KoinComponent {
    fun getCacheService(type: String, config: Any? = null): CacheService {
        return when (type) {
            "redis" -> get(named("redis")) {
                parametersOf(config as RedisConfig)
            }
            "memory" -> get(named("memory")) {
                config?.let { parametersOf(it) } ?: emptyParametersHolder()
            }
            else -> throw IllegalArgumentException("Unknown cache type: $type")
        }
    }
}

Advanced Patterns

Factory Functions with Qualifiers

class ServiceFactory : KoinComponent {
    fun createUserService(userId: String, tier: ServiceTier): UserService {
        return get(tier.qualifier) { parametersOf(userId) }
    }
    
    fun createRegionalService(region: Region, config: RegionalConfig): RegionalService {
        return get(region.qualifier) { parametersOf(config) }
    }
}

val factoryModule = module {
    factory<UserService>(ServiceTier.FREE.qualifier) { params ->
        val userId: String = params.get()
        FreeUserService(userId)
    }
    
    factory<UserService>(ServiceTier.PREMIUM.qualifier) { params ->
        val userId: String = params.get()
        PremiumUserService(userId, get<AnalyticsService>())
    }
}

Conditional Parameter Handling

val conditionalModule = module {
    factory<ConfigurableService> { params ->
        val config: ServiceConfig = params.get()
        val logger: Logger? = params.getOrNull()
        val metrics: MetricsService? = params.getOrNull()
        
        ConfigurableService(config).apply {
            logger?.let { setLogger(it) }
            metrics?.let { setMetrics(it) }
        }
    }
}

// Usage with optional parameters
val basicService: ConfigurableService = koin.get {
    parametersOf(ServiceConfig("basic"))
}

val advancedService: ConfigurableService = koin.get {
    parametersOf(
        ServiceConfig("advanced"),
        FileLogger("service.log"),
        PrometheusMetrics()
    )
}

Parameter Validation

val validationModule = module {
    factory<ValidatedService> { params ->
        val config: ServiceConfig = params.get()
        val timeout: Int = params.get()
        
        require(timeout > 0) { "Timeout must be positive" }
        require(config.isValid()) { "Invalid service configuration" }
        
        ValidatedService(config, timeout)
    }
}

Best Practices

1. Use Meaningful Qualifier Names

// Good - descriptive names
single<HttpClient>(named("user-api-client")) { /* ... */ }
single<Database>(named("primary-user-db")) { /* ... */ }

// Avoid - generic names
single<HttpClient>(named("client1")) { /* ... */ }
single<Database>(named("db")) { /* ... */ }

2. Consistent Qualifier Patterns

// Consistent naming pattern
val apiModule = module {
    single<ApiService>(named("user-api")) { /* ... */ }
    single<ApiService>(named("product-api")) { /* ... */ }
    single<ApiService>(named("order-api")) { /* ... */ }
}

3. Parameter Type Safety

// Good - explicit type handling
factory<Service> { params ->
    val config: ServiceConfig = params.get()
    val timeout: Int = params.get()
    Service(config, timeout)
}

// Better - with validation
factory<Service> { params ->
    val config: ServiceConfig = params.getOrNull()
        ?: throw IllegalArgumentException("ServiceConfig required")
    val timeout: Int = params.getOrNull() ?: DEFAULT_TIMEOUT
    Service(config, timeout)
}

4. Organize Qualifiers

object Qualifiers {
    val PRIMARY_DB = named("primary-database")
    val BACKUP_DB = named("backup-database")
    val CACHE_DB = named("cache-database")
    
    val USER_API = named("user-api-client")
    val ADMIN_API = named("admin-api-client")
}

val module = module {
    single<Database>(Qualifiers.PRIMARY_DB) { /* ... */ }
    single<ApiClient>(Qualifiers.USER_API) { /* ... */ }
}

Qualifiers and parameters provide powerful mechanisms for handling complex dependency injection scenarios while maintaining clean, type-safe code. They enable flexible, context-aware dependency resolution that scales with application complexity.

Install with Tessl CLI

npx tessl i tessl/maven-io-insert-koin--koin-core-iossimulatorarm64

docs

application-setup.md

components.md

dependency-injection.md

error-handling.md

index.md

modules-and-definitions.md

qualifiers-parameters.md

scoping.md

tile.json