Koin Core is a pragmatic lightweight dependency injection framework for Kotlin Multiplatform with iOS Simulator ARM64 target support.
—
This document covers the core dependency injection capabilities provided by the Koin class and how to resolve dependencies throughout your application.
Koin provides several ways to inject and resolve dependencies:
get() - Immediately resolve an instanceinject() - Defer resolution until first accessAll injection methods work through the Koin context, which acts as the main dependency resolution hub.
The Koin class is the central dependency injection context:
class Koin {
// Core resolution methods
inline fun <reified T : Any> get(
qualifier: Qualifier? = null,
noinline parameters: ParametersDefinition? = null
): T
inline fun <reified T : Any> inject(
qualifier: Qualifier? = null,
mode: LazyThreadSafetyMode = KoinPlatformTools.defaultLazyMode(),
noinline parameters: ParametersDefinition? = null
): Lazy<T>
// Nullable variants
inline fun <reified T : Any> getOrNull(/* ... */): T?
inline fun <reified T : Any> injectOrNull(/* ... */): Lazy<T?>
// Non-reified variants
fun <T> get(clazz: KClass<*>, qualifier: Qualifier?, parameters: ParametersDefinition?): T
fun <T> getOrNull(clazz: KClass<*>, qualifier: Qualifier?, parameters: ParametersDefinition?): T?
// Collection resolution
inline fun <reified T> getAll(): List<T>
// Runtime declaration
inline fun <reified T> declare(
instance: T,
qualifier: Qualifier? = null,
secondaryTypes: List<KClass<*>> = emptyList(),
allowOverride: Boolean = true
)
}Use get() to immediately resolve and obtain an instance:
import org.koin.core.*
import org.koin.dsl.*
// Define dependencies
val appModule = module {
single<Repository> { DatabaseRepository() }
factory<UseCase> { GetDataUseCase(get()) }
}
val koinApp = koinApplication { modules(appModule) }
val koin = koinApp.koin
// Direct resolution
val repository: Repository = koin.get()
val useCase: UseCase = koin.get()import org.koin.core.qualifier.named
val networkModule = module {
single<ApiService>(named("v1")) { ApiServiceV1() }
single<ApiService>(named("v2")) { ApiServiceV2() }
}
val koin = koinApplication { modules(networkModule) }.koin
// Resolve with qualifier
val apiV1: ApiService = koin.get(named("v1"))
val apiV2: ApiService = koin.get(named("v2"))import org.koin.core.parameter.parametersOf
val configModule = module {
factory<DatabaseConfig> { params ->
val url: String = params.get()
val port: Int = params.get()
DatabaseConfig(url, port)
}
}
val koin = koinApplication { modules(configModule) }.koin
// Resolve with parameters
val config: DatabaseConfig = koin.get {
parametersOf("localhost", 5432)
}For cases where you need to resolve using KClass:
import kotlin.reflect.KClass
val repository = koin.get<Repository>(
clazz = Repository::class,
qualifier = named("primary"),
parameters = { parametersOf("config") }
)Use inject() for lazy resolution - the instance is created only when first accessed:
val serviceModule = module {
single<HeavyService> { HeavyService() }
}
val koin = koinApplication { modules(serviceModule) }.koin
// Lazy injection - HeavyService not created yet
val service: Lazy<HeavyService> = koin.inject()
// HeavyService created here on first access
val actualService = service.valueControl how lazy instances handle concurrent access:
import org.koin.mp.KoinPlatformTools
// Default platform-specific mode
val service1: Lazy<Service> = koin.inject()
// Synchronized access (thread-safe)
val service2: Lazy<Service> = koin.inject(mode = LazyThreadSafetyMode.SYNCHRONIZED)
// No synchronization (fastest, but not thread-safe)
val service3: Lazy<Service> = koin.inject(mode = LazyThreadSafetyMode.NONE)
// Publication safety (thread-safe initialization, but allows multiple writes)
val service4: Lazy<Service> = koin.inject(mode = LazyThreadSafetyMode.PUBLICATION)val parameterizedService: Lazy<ConfigurableService> = koin.inject {
parametersOf("production", 30)
}
// Parameters evaluated when lazy value is accessed
val service = parameterizedService.valueHandle optional dependencies gracefully:
val optionalModule = module {
single<RequiredService> { RequiredService() }
// OptionalService not defined
}
val koin = koinApplication { modules(optionalModule) }.koin
// Required service - will succeed
val required: RequiredService = koin.get()
// Optional service - returns null instead of throwing
val optional: OptionalService? = koin.getOrNull()
if (optional != null) {
// Use optional service
optional.doSomething()
}val optionalService: Lazy<OptionalService?> = koin.injectOrNull()
// Check if service is available when accessed
val service = optionalService.value
if (service != null) {
service.performAction()
}val optionalConfig: DatabaseConfig? = koin.getOrNull(
qualifier = named("test"),
parameters = { parametersOf("test-db") }
)Get all registered instances of a specific type:
val pluginModule = module {
single<Plugin> { PluginA() }
single<Plugin> { PluginB() }
single<Plugin> { PluginC() }
}
val koin = koinApplication { modules(pluginModule) }.koin
// Get all Plugin instances
val allPlugins: List<Plugin> = koin.getAll()
allPlugins.forEach { plugin ->
plugin.initialize()
}val handlerModule = module {
single<EventHandler>(named("auth")) { AuthEventHandler() }
single<EventHandler>(named("payment")) { PaymentEventHandler() }
factory<EventHandler>(named("logging")) { LoggingEventHandler() }
}
val koin = koinApplication { modules(handlerModule) }.koin
// Gets all EventHandler instances regardless of qualifiers
val allHandlers: List<EventHandler> = koin.getAll()Register instances dynamically at runtime:
val koin = koinApplication { modules(emptyModule) }.koin
// Declare an instance at runtime
val runtimeConfig = RuntimeConfig("dynamic-value")
koin.declare(runtimeConfig)
// Now it can be resolved
val config: RuntimeConfig = koin.get()// Declare with specific qualifier
koin.declare(
instance = DatabaseConnection("prod-db"),
qualifier = named("production")
)
// Resolve with qualifier
val prodDb: DatabaseConnection = koin.get(named("production"))interface Repository
interface ReadOnlyRepository
class DatabaseRepository : Repository, ReadOnlyRepository
val dbRepo = DatabaseRepository()
// Bind to multiple types
koin.declare(
instance = dbRepo,
secondaryTypes = listOf(ReadOnlyRepository::class),
allowOverride = false
)
// Can resolve as either type
val repo: Repository = koin.get()
val readOnly: ReadOnlyRepository = koin.get()// First declaration
koin.declare(UserService("v1"))
// Override existing - will succeed with allowOverride = true
koin.declare(
instance = UserService("v2"),
allowOverride = true
)
// This would fail with allowOverride = false
// koin.declare(UserService("v3"), allowOverride = false) // Exception!For applications using a global Koin context:
import org.koin.core.context.*
// Start global context
fun startKoin(koinApplication: KoinApplication): KoinApplication
fun startKoin(appDeclaration: KoinAppDeclaration): KoinApplication
// Stop global context
fun stopKoin(): Unit
// Module management in global context
fun loadKoinModules(module: Module): Unit
fun loadKoinModules(modules: List<Module>): Unit
fun unloadKoinModules(module: Module): Unit
fun unloadKoinModules(modules: List<Module>): Unitimport org.koin.core.context.*
import org.koin.dsl.*
// Start global context
startKoin {
modules(appModule)
}
// The global context is now available through KoinPlatformTools
// Components can access it without explicit Koin referenceDependencies can also be resolved within specific scopes (covered in detail in Scope Management):
val scopedModule = module {
scope<UserSession> {
scoped<UserData> { UserData(get()) }
}
}
val koin = koinApplication { modules(scopedModule) }.koin
val userScope = koin.createScope<UserSession>("user-123")
// Resolve within scope
val userData: UserData = userScope.get()// Good - lazy initialization
val heavyService: Lazy<HeavyService> = koin.inject()
// Use only when needed
if (someCondition) {
heavyService.value.performOperation()
}class OptionalFeatureService(
private val required: RequiredService,
private val optional: OptionalService? = null
) {
fun performAction() {
required.execute()
optional?.enhanceExecution()
}
}
val service = OptionalFeatureService(
required = koin.get(),
optional = koin.getOrNull()
)// Initialize all plugins
val plugins: List<Plugin> = koin.getAll()
plugins.forEach { it.initialize() }
// Process with handlers
val handlers: List<EventHandler> = koin.getAll()
val event = Event("user.login")
handlers.forEach { handler ->
handler.handle(event)
}class ConfigurableApplication {
fun configureEnvironment(environment: String, koin: Koin) {
when (environment) {
"development" -> {
koin.declare(DevConfig(), named("env"))
}
"production" -> {
koin.declare(ProdConfig(), named("env"))
}
}
}
}This flexible resolution system enables clean dependency injection patterns while maintaining type safety and supporting various usage scenarios from simple direct resolution to complex lazy initialization strategies.
Install with Tessl CLI
npx tessl i tessl/maven-io-insert-koin--koin-core-iossimulatorarm64