Koin Core is a pragmatic lightweight dependency injection framework for Kotlin Multiplatform with iOS Simulator ARM64 target support.
—
This document covers how to organize your dependencies using Koin's module system and define different types of components with the DSL.
Koin modules are containers that organize your dependency definitions. The module DSL provides several definition types:
single) - One instance shared across the applicationfactory) - New instance created on each requestscoped) - Instance tied to a specific scope lifecycleModules can be composed, combined, and loaded dynamically to build flexible dependency injection architectures.
import org.koin.dsl.module
fun module(createdAtStart: Boolean = false, moduleDeclaration: ModuleDeclaration): Module
typealias ModuleDeclaration = Module.() -> Unitimport org.koin.dsl.*
val appModule = module {
single<Repository> { DatabaseRepository() }
factory<UseCase> { GetDataUseCase(get()) }
}val databaseModule = module(createdAtStart = true) {
single<Database> { DatabaseConnection() }
single<Migrator> { DatabaseMigrator(get()) }
}The Module class provides the container and DSL for dependency definitions:
class Module(@PublishedApi internal val _createdAtStart: Boolean = false) {
val id: String
val isLoaded: Boolean
var eagerInstances: LinkedHashSet<SingleInstanceFactory<*>>
// Core definition methods
inline fun <reified T> single(
qualifier: Qualifier? = null,
createdAtStart: Boolean = false,
noinline definition: Definition<T>
): KoinDefinition<T>
inline fun <reified T> factory(
qualifier: Qualifier? = null,
noinline definition: Definition<T>
): KoinDefinition<T>
// Scope definitions
fun scope(qualifier: Qualifier, scopeSet: ScopeDSL.() -> Unit)
inline fun <reified T> scope(scopeSet: ScopeDSL.() -> Unit)
// Module composition
fun includes(vararg module: Module)
fun includes(module: Collection<Module>)
operator fun plus(module: Module) = listOf(this, module)
operator fun plus(modules: List<Module>) = listOf(this) + modules
}
// Type aliases
typealias Definition<T> = Scope.(ParametersHolder) -> TSingletons are created once and reused throughout the application lifecycle.
val singletonModule = module {
// Simple singleton
single<Logger> { FileLogger() }
// Singleton with dependencies
single<UserService> { UserService(get(), get()) }
}import org.koin.core.qualifier.named
val configModule = module {
single<Config>(named("dev")) { DevConfig() }
single<Config>(named("prod")) { ProductionConfig() }
}val eagerModule = module {
// Eager singleton - created at startup
single<DatabaseConnection>(createdAtStart = true) {
DatabaseConnection()
}
// Regular singleton - created when first requested
single<UserRepository> { UserRepository(get()) }
}val parameterizedModule = module {
single<ApiClient> { params ->
val baseUrl: String = params.get()
val timeout: Int = params.get()
ApiClient(baseUrl, timeout)
}
}
// Usage:
val apiClient = koin.get<ApiClient> {
parametersOf("https://api.example.com", 30000)
}Factories create a new instance each time they're requested.
val factoryModule = module {
// New instance on each get()
factory<RequestHandler> { RequestHandler() }
// Factory with dependencies
factory<EmailService> { EmailService(get(), get()) }
}val comparisonModule = module {
single<DatabaseConnection> { DatabaseConnection() } // Shared
factory<Transaction> { Transaction(get()) } // New each time
}
// Usage demonstrates difference:
val conn1 = koin.get<DatabaseConnection>() // Same instance
val conn2 = koin.get<DatabaseConnection>() // Same instance (conn1 === conn2)
val tx1 = koin.get<Transaction>() // New instance
val tx2 = koin.get<Transaction>() // New instance (tx1 !== tx2)val complexModule = module {
factory<ReportGenerator> { params ->
val reportType: String = params.get()
val format: String = params.get()
when (reportType) {
"sales" -> SalesReportGenerator(format, get())
"inventory" -> InventoryReportGenerator(format, get())
else -> DefaultReportGenerator(format)
}
}
}Bind definitions to additional types for polymorphic resolution:
import org.koin.dsl.*
// Bind to additional class
infix fun <S : Any> KoinDefinition<out S>.bind(clazz: KClass<S>): KoinDefinition<out S>
// Reified binding
inline fun <reified S : Any> KoinDefinition<out S>.bind(): KoinDefinition<out S>
// Bind to multiple classes
infix fun KoinDefinition<*>.binds(classes: Array<KClass<*>>): KoinDefinition<*>
// Close callback
infix fun <T> KoinDefinition<T>.onClose(onClose: OnCloseCallback<T>): KoinDefinition<T>interface Repository
interface ReadOnlyRepository
class DatabaseRepository : Repository, ReadOnlyRepository
val repositoryModule = module {
// Bind to multiple interfaces
single<DatabaseRepository> { DatabaseRepository() }
.bind<Repository>()
.bind<ReadOnlyRepository>()
// Alternative syntax
single<DatabaseRepository> { DatabaseRepository() }
.binds(arrayOf(Repository::class, ReadOnlyRepository::class))
}
// Can resolve as any bound type:
val db: DatabaseRepository = koin.get()
val repo: Repository = koin.get() // Same instance as db
val readOnly: ReadOnlyRepository = koin.get() // Same instance as dbval resourceModule = module {
single<FileResource> { FileResource("/tmp/data") }
.onClose { resource ->
resource.cleanup()
println("Resource cleaned up")
}
}Koin provides constructor-reference DSL for compile-time type safety:
import org.koin.core.module.dsl.*
// Zero-argument constructor
inline fun <reified R> Module.singleOf(
crossinline constructor: () -> R,
noinline options: DefinitionOptions<R>? = null
): KoinDefinition<R>
// Constructors with 1-22 parameters
inline fun <reified R, reified T1> Module.singleOf(
crossinline constructor: (T1) -> R,
noinline options: DefinitionOptions<R>? = null
): KoinDefinition<R>
// ... up to T1, T2, ..., T22Similar pattern for factory definitions:
inline fun <reified R> Module.factoryOf(
crossinline constructor: () -> R,
noinline options: DefinitionOptions<R>? = null
): KoinDefinition<R>
// ... with parametersclass UserService(private val repo: UserRepository, private val logger: Logger)
class UserRepository(private val database: Database)
class Logger()
val constructorModule = module {
// Using constructor references
singleOf(::Logger)
singleOf(::UserRepository) // Automatically resolves Database dependency
singleOf(::UserService) // Resolves UserRepository and Logger
// Equivalent traditional syntax:
// single<Logger> { Logger() }
// single<UserRepository> { UserRepository(get()) }
// single<UserService> { UserService(get(), get()) }
}val optionsModule = module {
singleOf(::DatabaseConnection) { options ->
options.createdAtStart()
options.onClose { it.disconnect() }
}
factoryOf(::HttpClient) { options ->
options.bind<Client>()
}
}Scoped definitions are covered in detail in Scope Management, but here's an overview:
class ScopeDSL(val scopeQualifier: Qualifier, val module: Module) {
inline fun <reified T> scoped(
qualifier: Qualifier? = null,
noinline definition: Definition<T>
): KoinDefinition<T>
inline fun <reified T> factory(
qualifier: Qualifier? = null,
noinline definition: Definition<T>
): KoinDefinition<T>
}import org.koin.core.qualifier.named
val scopedModule = module {
scope(named("session")) {
scoped<UserSession> { UserSession() }
scoped<ShoppingCart> { ShoppingCart(get()) }
factory<TempData> { TempData() } // New instance each time within scope
}
}class UserSession
val typedScopeModule = module {
scope<UserSession> {
scoped<UserPreferences> { UserPreferences() }
scoped<UserCache> { UserCache() }
}
}val databaseModule = module {
single<Database> { Database() }
}
val repositoryModule = module {
single<UserRepository> { UserRepository(get()) }
}
val appModule = module {
// Include other modules
includes(databaseModule, repositoryModule)
// Add own definitions
single<UserService> { UserService(get()) }
}val coreModules = databaseModule + repositoryModule
val allModules = coreModules + appModule
// Load combined modules
val app = koinApplication {
modules(allModules)
}fun createAppModule(isProduction: Boolean): Module = module {
includes(coreModule)
if (isProduction) {
includes(productionModule)
} else {
includes(developmentModule)
}
}val environmentModule = module {
single<Config> {
val env = System.getenv("ENVIRONMENT") ?: "development"
when (env) {
"production" -> ProductionConfig()
"test" -> TestConfig()
else -> DevelopmentConfig()
}
}
}fun createServiceModule(apiKey: String): Module = module {
single<ApiService> { ApiService(apiKey) }
factory<ApiClient> { ApiClient(get()) }
}
// Usage:
val serviceModule = createServiceModule("prod-key-123")interface Cache<T>
class InMemoryCache<T> : Cache<T>
val cacheModule = module {
single<Cache<String>> { InMemoryCache<String>() }
single<Cache<Int>> { InMemoryCache<Int>() }
single<Cache<User>> { InMemoryCache<User>() }
}import org.koin.core.module.dsl.OptionDSL
class OptionDSL<T> {
fun createdAtStart()
fun onClose(callback: OnCloseCallback<T>)
}val optionModule = module {
single<ConnectionPool> { ConnectionPool() } onClose { pool ->
pool.shutdown()
}
single<Cache> { RedisCache() } bind Cache::class
}// User feature module
val userModule = module {
single<UserRepository> { UserRepository(get()) }
factory<UserService> { UserService(get()) }
}
// Payment feature module
val paymentModule = module {
single<PaymentGateway> { PaymentGateway(get()) }
factory<PaymentProcessor> { PaymentProcessor(get()) }
}// Preferred - type-safe at compile time
val preferredModule = module {
singleOf(::UserService)
factoryOf(::EmailService)
}
// Avoid - runtime resolution
val avoidModule = module {
single<UserService> { UserService(get(), get()) }
}// Configuration module
val configModule = module {
single<DatabaseUrl> { DatabaseUrl("jdbc:postgresql://localhost/app") }
single<ApiTimeout> { ApiTimeout(30000) }
}
// Service module using configuration
val serviceModule = module {
single<DatabaseService> { DatabaseService(get<DatabaseUrl>().value) }
single<ApiService> { ApiService(get<ApiTimeout>().value) }
}import org.koin.core.qualifier.named
val multiVariantModule = module {
single<HttpClient>(named("authenticated")) {
HttpClient().apply {
addAuthInterceptor()
}
}
single<HttpClient>(named("public")) {
HttpClient()
}
}This module system provides a clean, flexible way to organize dependencies while maintaining clear separation of concerns and supporting various architectural patterns.
Install with Tessl CLI
npx tessl i tessl/maven-io-insert-koin--koin-core-iossimulatorarm64