Koin Core is a pragmatic lightweight dependency injection framework for Kotlin Multiplatform with iOS Simulator ARM64 target support.
—
This document covers how to integrate dependency injection into your classes using Koin's component interfaces and extension functions.
Koin provides component interfaces that enable seamless dependency injection integration:
These interfaces provide extension functions that allow your classes to directly access the dependency injection container without explicitly passing around Koin instances.
The basic interface for dependency injection integration:
interface KoinComponent {
fun getKoin(): Koin
}// Direct resolution
inline fun <reified T : Any> KoinComponent.get(
qualifier: Qualifier? = null,
noinline parameters: ParametersDefinition? = null
): T
// Lazy injection
inline fun <reified T : Any> KoinComponent.inject(
qualifier: Qualifier? = null,
mode: LazyThreadSafetyMode = KoinPlatformTools.defaultLazyMode(),
noinline parameters: ParametersDefinition? = null
): Lazy<T>import org.koin.core.component.KoinComponent
import org.koin.core.component.get
import org.koin.core.component.inject
class UserService : KoinComponent {
// Direct injection - resolved immediately
private val repository: UserRepository = get()
// Lazy injection - resolved when first accessed
private val logger: Logger by inject()
// Qualified injection
private val cache: Cache = get(named("user"))
fun processUser(userId: String) {
logger.info("Processing user: $userId")
val user = repository.findById(userId)
// ... process user
}
}import org.koin.core.parameter.parametersOf
class ConfigurableService : KoinComponent {
fun createClient(endpoint: String, timeout: Int): ApiClient {
return get { parametersOf(endpoint, timeout) }
}
fun getEnvironmentConfig(): Config {
val env = System.getenv("ENVIRONMENT") ?: "development"
return get(named(env))
}
}class IsolatedService(private val customKoin: Koin) : KoinComponent {
// Override to use custom Koin instance
override fun getKoin(): Koin = customKoin
private val service: DataService by inject()
}The scope-aware interface that automatically manages scoped dependencies:
interface KoinScopeComponent : KoinComponent {
val scope: Scope
}// Scope identification
fun <T : Any> T.getScopeId(): String
fun <T : Any> T.getScopeName(): TypeQualifier
// Scope creation
fun <T : KoinScopeComponent> T.createScope(
scopeId: ScopeID = getScopeId(),
source: Any? = null,
scopeArchetype: TypeQualifier? = null
): Scope
fun <T : KoinScopeComponent> T.createScope(source: Any? = null): Scope
// Scope access
fun <T : KoinScopeComponent> T.getScopeOrNull(): Scope?
// Lazy scope creation
fun <T : KoinScopeComponent> T.newScope(): Lazy<Scope>
fun <T : KoinScopeComponent> T.getOrCreateScope(): Lazy<Scope>import org.koin.core.component.KoinScopeComponent
import org.koin.core.component.createScope
import org.koin.core.component.get
import org.koin.core.scope.Scope
class UserSession : KoinScopeComponent {
// Automatically creates scope for this instance
override val scope: Scope by lazy { createScope() }
// Scoped dependencies - shared within this session
private val sessionData: SessionData by inject()
private val preferences: UserPreferences by inject()
fun login(credentials: Credentials) {
// Dependencies are resolved from this instance's scope
sessionData.initialize(credentials.userId)
preferences.load(credentials.userId)
}
fun logout() {
// Close scope when session ends
scope.close()
}
}class CustomScopeComponent : KoinScopeComponent {
private var _scope: Scope? = null
override val scope: Scope
get() = _scope ?: throw IllegalStateException("Scope not initialized")
fun initialize() {
_scope = createScope()
}
fun cleanup() {
_scope?.close()
_scope = null
}
}When using KoinScopeComponent, the get() and inject() functions automatically use the component's scope:
class ScopedService : KoinScopeComponent {
override val scope: Scope by lazy { createScope() }
fun example() {
// These resolve from the component's scope
val service1: MyService = get() // scope.get<MyService>()
val service2: MyService by inject() // scope.inject<MyService>()
// Equivalent explicit calls
val service3: MyService = scope.get()
val service4: MyService by scope.inject()
}
}// Business service with dependency injection
class OrderService : KoinComponent {
private val orderRepository: OrderRepository by inject()
private val paymentService: PaymentService by inject()
private val emailService: EmailService by inject()
private val logger: Logger by inject()
fun processOrder(order: Order): OrderResult {
logger.info("Processing order ${order.id}")
return try {
// Validate order
val validatedOrder = validateOrder(order)
// Process payment
val payment = paymentService.processPayment(validatedOrder.total)
// Save order
val savedOrder = orderRepository.save(validatedOrder.copy(paymentId = payment.id))
// Send confirmation
emailService.sendOrderConfirmation(savedOrder)
OrderResult.Success(savedOrder)
} catch (e: Exception) {
logger.error("Failed to process order ${order.id}", e)
OrderResult.Failure(e.message)
}
}
private fun validateOrder(order: Order): Order {
// Validation logic
return order
}
}// Android ViewModel example
class UserViewModel : KoinComponent {
private val userService: UserService by inject()
private val analyticsService: AnalyticsService by inject(named("firebase"))
private val _users = MutableLiveData<List<User>>()
val users: LiveData<List<User>> = _users
fun loadUsers() {
viewModelScope.launch {
try {
val userList = userService.getAllUsers()
_users.value = userList
analyticsService.track("users_loaded", mapOf("count" to userList.size))
} catch (e: Exception) {
// Handle error
}
}
}
}class RequestHandler : KoinScopeComponent {
// Each request gets its own scope
override val scope: Scope by lazy { createScope() }
private val requestData: RequestData by inject()
private val auditLogger: AuditLogger by inject()
fun handleRequest(request: HttpRequest): HttpResponse {
try {
// Process with scoped dependencies
auditLogger.logRequest(request)
val result = processRequest(request)
auditLogger.logResponse(result)
return result
} finally {
// Always cleanup scope
scope.close()
}
}
private fun processRequest(request: HttpRequest): HttpResponse {
// Business logic using scoped dependencies
return HttpResponse.ok()
}
}class ServiceFactory : KoinComponent {
fun createUserService(userId: String): UserService {
// Inject dependencies for the service
val repository: UserRepository = get()
val cache: Cache = get(named("user"))
return UserService(userId, repository, cache)
}
fun createAdminService(adminLevel: Int): AdminService {
return get { parametersOf(adminLevel) }
}
}class MultiScopeComponent : KoinComponent {
private val globalKoin = getKoin()
// Different scopes for different contexts
private val sessionScope: Scope by lazy {
globalKoin.createScope<UserSession>("session-${sessionId()}")
}
private val requestScope: Scope by lazy {
globalKoin.createScope<RequestContext>("request-${requestId()}")
}
fun getSessionService(): SessionService = sessionScope.get()
fun getRequestService(): RequestService = requestScope.get()
private fun sessionId(): String = "session-123"
private fun requestId(): String = "request-456"
}class ConditionalComponent : KoinComponent {
private val featureFlag: FeatureFlag by inject()
// Conditional lazy injection based on feature flags
private val newFeatureService: NewFeatureService? by lazy {
if (featureFlag.isEnabled("new_feature")) {
get<NewFeatureService>()
} else {
null
}
}
fun performAction() {
if (newFeatureService != null) {
newFeatureService.performNewAction()
} else {
// Fallback behavior
performLegacyAction()
}
}
private fun performLegacyAction() {
// Legacy implementation
}
}abstract class BaseComponent : KoinComponent {
protected val logger: Logger by inject()
protected val config: Config by inject()
protected fun logOperation(operation: String) {
logger.info("$operation in ${this::class.simpleName}")
}
}
class UserComponent : BaseComponent() {
private val userService: UserService by inject()
fun processUser(userId: String) {
logOperation("processUser") // Uses inherited logger
userService.process(userId)
}
}
class OrderComponent : BaseComponent() {
private val orderService: OrderService by inject()
fun processOrder(orderId: String) {
logOperation("processOrder") // Uses inherited logger
orderService.process(orderId)
}
}class TestableComponent : KoinComponent {
private val service: MyService by inject()
fun doSomething(): String {
return service.getData()
}
}
// Test with mock
@Test
fun testComponent() {
val testModule = module {
single<MyService> { mockk<MyService> {
every { getData() } returns "test data"
}}
}
val testApp = koinApplication { modules(testModule) }
val component = TestableComponent()
// Component uses test Koin context automatically
assertEquals("test data", component.doSomething())
}@Test
fun testScopedComponent() {
val testModule = module {
scope<TestScope> {
scoped<ScopedService> { mockk<ScopedService>() }
}
}
val koin = koinApplication { modules(testModule) }.koin
class TestComponent : KoinScopeComponent {
override val scope = koin.createScope<TestScope>("test")
fun getService(): ScopedService = get()
}
val component = TestComponent()
assertNotNull(component.getService())
component.scope.close()
}class OptimizedComponent : KoinComponent {
// Lazy injection - only created when needed
private val expensiveService: ExpensiveService by inject()
// Direct injection for lightweight dependencies
private val config: Config = get()
fun performOperation() {
// ExpensiveService is created here, not at component creation
expensiveService.doWork()
}
}class ProperScopeComponent : KoinScopeComponent {
override val scope: Scope by lazy { createScope() }
fun initialize() {
// Setup scope dependencies
}
fun cleanup() {
// Always close scope when done
if (!scope.closed) {
scope.close()
}
}
}// Good - uses component interface
class GoodComponent : KoinComponent {
private val service: MyService by inject()
}
// Avoid - direct Koin dependency
class AvoidComponent(private val koin: Koin) {
private val service: MyService = koin.get()
}// Specific interfaces for different component types
interface ServiceComponent : KoinComponent
interface RepositoryComponent : KoinComponent
interface ScopedServiceComponent : KoinScopeComponent
class UserService : ServiceComponent {
private val repository: UserRepository by inject()
}
class UserSessionManager : ScopedServiceComponent {
override val scope: Scope by lazy { createScope() }
private val sessionData: SessionData by inject()
}Component interfaces provide a clean, testable way to integrate dependency injection into your application architecture while maintaining loose coupling and supporting various lifecycle patterns.
Install with Tessl CLI
npx tessl i tessl/maven-io-insert-koin--koin-core-iossimulatorarm64