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

dependency-injection.mddocs/

Core Dependency Injection

This document covers the core dependency injection capabilities provided by the Koin class and how to resolve dependencies throughout your application.

Overview

Koin provides several ways to inject and resolve dependencies:

  • Direct resolution using get() - Immediately resolve an instance
  • Lazy injection using inject() - Defer resolution until first access
  • Nullable variants - Handle optional dependencies gracefully
  • Collection resolution - Get all instances of a type
  • Instance declaration - Register instances at runtime

All injection methods work through the Koin context, which acts as the main dependency resolution hub.

Koin Context

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

Direct Resolution with get()

Use get() to immediately resolve and obtain an instance:

Basic Usage

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

With Qualifiers

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

With Parameters

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

Non-Reified Resolution

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

Lazy Injection with inject()

Use inject() for lazy resolution - the instance is created only when first accessed:

Basic Lazy Injection

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.value

Thread Safety Modes

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

Lazy Injection with Parameters

val parameterizedService: Lazy<ConfigurableService> = koin.inject {
    parametersOf("production", 30)
}

// Parameters evaluated when lazy value is accessed
val service = parameterizedService.value

Nullable Resolution

Handle optional dependencies gracefully:

Nullable Direct Resolution

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

Nullable Lazy Injection

val optionalService: Lazy<OptionalService?> = koin.injectOrNull()

// Check if service is available when accessed
val service = optionalService.value
if (service != null) {
    service.performAction()
}

Nullable with Qualifiers and Parameters

val optionalConfig: DatabaseConfig? = koin.getOrNull(
    qualifier = named("test"),
    parameters = { parametersOf("test-db") }
)

Collection Resolution

Get all registered instances of a specific type:

Basic Collection Resolution

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

With Different Qualifiers

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

Runtime Instance Declaration

Register instances dynamically at runtime:

Basic Declaration

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

Declaration with Qualifiers

// Declare with specific qualifier
koin.declare(
    instance = DatabaseConnection("prod-db"),
    qualifier = named("production")
)

// Resolve with qualifier
val prodDb: DatabaseConnection = koin.get(named("production"))

Multiple Type Bindings

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

Override Control

// 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!

Global Context Access

For applications using a global Koin context:

Starting Global 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>): Unit

Using Global Context

import 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 reference

Scoped Resolution

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

Best Practices

1. Prefer Lazy Injection for Expensive Resources

// Good - lazy initialization
val heavyService: Lazy<HeavyService> = koin.inject()

// Use only when needed
if (someCondition) {
    heavyService.value.performOperation()
}

2. Use Nullable Variants for Optional Dependencies

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

3. Handle Collections Appropriately

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

4. Runtime Declaration for Dynamic Configuration

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

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