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

modules-and-definitions.mddocs/

Module System & Definitions

This document covers how to organize your dependencies using Koin's module system and define different types of components with the DSL.

Overview

Koin modules are containers that organize your dependency definitions. The module DSL provides several definition types:

  • Singletons (single) - One instance shared across the application
  • Factories (factory) - New instance created on each request
  • Scoped instances (scoped) - Instance tied to a specific scope lifecycle

Modules can be composed, combined, and loaded dynamically to build flexible dependency injection architectures.

Module Creation

Basic Module Definition

import org.koin.dsl.module

fun module(createdAtStart: Boolean = false, moduleDeclaration: ModuleDeclaration): Module

typealias ModuleDeclaration = Module.() -> Unit

Simple Module Example

import org.koin.dsl.*

val appModule = module {
    single<Repository> { DatabaseRepository() }
    factory<UseCase> { GetDataUseCase(get()) }
}

Module with Eager Initialization

val databaseModule = module(createdAtStart = true) {
    single<Database> { DatabaseConnection() }
    single<Migrator> { DatabaseMigrator(get()) }
}

Module Class

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) -> T

Singleton Definitions

Singletons are created once and reused throughout the application lifecycle.

Basic Singleton

val singletonModule = module {
    // Simple singleton
    single<Logger> { FileLogger() }
    
    // Singleton with dependencies
    single<UserService> { UserService(get(), get()) }
}

Singleton with Qualifier

import org.koin.core.qualifier.named

val configModule = module {
    single<Config>(named("dev")) { DevConfig() }
    single<Config>(named("prod")) { ProductionConfig() }
}

Eager Singletons

val eagerModule = module {
    // Eager singleton - created at startup
    single<DatabaseConnection>(createdAtStart = true) { 
        DatabaseConnection() 
    }
    
    // Regular singleton - created when first requested
    single<UserRepository> { UserRepository(get()) }
}

Singleton with Parameters

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) 
}

Factory Definitions

Factories create a new instance each time they're requested.

Basic Factory

val factoryModule = module {
    // New instance on each get()
    factory<RequestHandler> { RequestHandler() }
    
    // Factory with dependencies
    factory<EmailService> { EmailService(get(), get()) }
}

Factory vs Singleton Comparison

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)

Factory with Complex Logic

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)
        }
    }
}

Definition Binding

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 Binding Examples

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 db

Cleanup Callbacks

val resourceModule = module {
    single<FileResource> { FileResource("/tmp/data") }
        .onClose { resource -> 
            resource.cleanup()
            println("Resource cleaned up")
        }
}

Constructor-Based Definitions

Koin provides constructor-reference DSL for compile-time type safety:

SingleOf Functions

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, ..., T22

FactoryOf Functions

Similar pattern for factory definitions:

inline fun <reified R> Module.factoryOf(
    crossinline constructor: () -> R,
    noinline options: DefinitionOptions<R>? = null
): KoinDefinition<R>

// ... with parameters

Constructor DSL Examples

class 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()) }
}

Constructor Options

val optionsModule = module {
    singleOf(::DatabaseConnection) { options ->
        options.createdAtStart()
        options.onClose { it.disconnect() }
    }
    
    factoryOf(::HttpClient) { options ->
        options.bind<Client>()
    }
}

Scoped Definitions

Scoped definitions are covered in detail in Scope Management, but here's an overview:

Scope Definition DSL

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>
}

Basic Scope Usage

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
    }
}

Type-Based Scopes

class UserSession

val typedScopeModule = module {
    scope<UserSession> {
        scoped<UserPreferences> { UserPreferences() }
        scoped<UserCache> { UserCache() }
    }
}

Module Composition

Including Modules

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()) }
}

Module Combination

val coreModules = databaseModule + repositoryModule
val allModules = coreModules + appModule

// Load combined modules
val app = koinApplication {
    modules(allModules)
}

Conditional Module Inclusion

fun createAppModule(isProduction: Boolean): Module = module {
    includes(coreModule)
    
    if (isProduction) {
        includes(productionModule)
    } else {
        includes(developmentModule)
    }
}

Advanced Definition Patterns

Conditional Definitions

val environmentModule = module {
    single<Config> {
        val env = System.getenv("ENVIRONMENT") ?: "development"
        when (env) {
            "production" -> ProductionConfig()
            "test" -> TestConfig()
            else -> DevelopmentConfig()
        }
    }
}

Definition Factories

fun createServiceModule(apiKey: String): Module = module {
    single<ApiService> { ApiService(apiKey) }
    factory<ApiClient> { ApiClient(get()) }
}

// Usage:
val serviceModule = createServiceModule("prod-key-123")

Generic Type Handling

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>() }
}

Definition Options & DSL

Option DSL

import org.koin.core.module.dsl.OptionDSL

class OptionDSL<T> {
    fun createdAtStart()
    fun onClose(callback: OnCloseCallback<T>)
}

Using Options

val optionModule = module {
    single<ConnectionPool> { ConnectionPool() } onClose { pool ->
        pool.shutdown()
    }
    
    single<Cache> { RedisCache() } bind Cache::class
}

Best Practices

1. Organize by Feature

// 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()) }
}

2. Use Constructor References

// Preferred - type-safe at compile time
val preferredModule = module {
    singleOf(::UserService)
    factoryOf(::EmailService)
}

// Avoid - runtime resolution
val avoidModule = module {
    single<UserService> { UserService(get(), get()) }
}

3. Separate Configuration from Logic

// 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) }
}

4. Use Qualifiers for Variants

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

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