Koin Core is a pragmatic lightweight dependency injection framework for Kotlin Multiplatform with iOS Simulator ARM64 target support.
—
This document covers how to distinguish between similar dependencies using qualifiers and how to pass runtime parameters during dependency resolution.
Koin provides two key mechanisms for handling complex dependency scenarios:
These features enable sophisticated dependency injection patterns while maintaining type safety and clarity.
Qualifiers help distinguish between multiple definitions of the same type, enabling you to have different implementations or configurations for different contexts.
interface Qualifier {
val value: QualifierValue
}
typealias QualifierValue = String// 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(): TypeQualifierdata class StringQualifier(override val value: QualifierValue) : Qualifier
class TypeQualifier(val type: KClass<*>) : Qualifier {
override val value: QualifierValue
}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"))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))
}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() }
}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
}
}// 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>())
}// 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>()
}
}Parameters allow you to pass runtime values during dependency resolution, enabling dynamic configuration and context-specific instantiation.
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// 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(): ParametersHolderval 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")
}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")
}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
)
}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)
}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)
}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")
}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")
}
}
}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>())
}
}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()
)
}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)
}
}// 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")) { /* ... */ }// Consistent naming pattern
val apiModule = module {
single<ApiService>(named("user-api")) { /* ... */ }
single<ApiService>(named("product-api")) { /* ... */ }
single<ApiService>(named("order-api")) { /* ... */ }
}// 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)
}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