kotlinintermediate

Dependency Injection with Koin

Set up Koin DI: modules, scoped instances, factory vs single, and parameterized injection.

kotlin
import org.koin.core.context.startKoin
import org.koin.core.context.stopKoin
import org.koin.core.module.dsl.*
import org.koin.core.parameter.parametersOf
import org.koin.dsl.*
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import org.koin.core.qualifier.named

// Interfaces
interface Logger {
    fun log(message: String)
}

interface UserRepository {
    fun findById(id: String): Map<String, Any>?
    fun save(user: Map<String, Any>)
}

interface NotificationService {
    fun send(userId: String, message: String)
}

// Implementations
class ConsoleLogger : Logger {
    override fun log(message: String) = println("[LOG] $message")
}

class InMemoryUserRepository(private val logger: Logger) : UserRepository {
    private val users = mutableMapOf<String, Map<String, Any>>()

    override fun findById(id: String): Map<String, Any>? {
        logger.log("Finding user: $id")
        return users[id]
    }

    override fun save(user: Map<String, Any>) {
        val id = user["id"] as String
        logger.log("Saving user: $id")
        users[id] = user
    }
}

class EmailNotificationService(
    private val logger: Logger,
    private val smtpHost: String
) : NotificationService {
    override fun send(userId: String, message: String) {
        logger.log("Sending email via $smtpHost to $userId: $message")
    }
}

// Service with dependencies
class UserService(
    private val repo: UserRepository,
    private val notification: NotificationService,
    private val logger: Logger
) {
    fun createUser(id: String, name: String) {
        logger.log("Creating user $id")
        repo.save(mapOf("id" to id, "name" to name))
        notification.send(id, "Welcome, $name!")
    }

    fun getUser(id: String) = repo.findById(id)
}

// Parameterized factory
class ApiClient(val baseUrl: String, private val logger: Logger) {
    fun get(path: String): String {
        logger.log("GET $baseUrl$path")
        return "{\"status\": \"ok\"}"
    }
}

// Koin modules
val coreModule = module {
    single<Logger> { ConsoleLogger() }
}

val dataModule = module {
    single<UserRepository> { InMemoryUserRepository(get()) }
}

val serviceModule = module {
    single<NotificationService> {
        EmailNotificationService(get(), "smtp.example.com")
    }
    single { UserService(get(), get(), get()) }

    // Factory — new instance each time
    factory { (baseUrl: String) ->
        ApiClient(baseUrl, get())
    }

    // Named instances
    single(named("primary")) { mapOf("url" to "https://primary.api.com") }
    single(named("fallback")) { mapOf("url" to "https://fallback.api.com") }
}

// KoinComponent — for classes outside DI graph
class Application : KoinComponent {
    private val userService: UserService by inject()
    private val logger: Logger by inject()

    fun run() {
        logger.log("Application starting")
        userService.createUser("1", "Alice")
        userService.createUser("2", "Bob")

        val user = userService.getUser("1")
        logger.log("Found: $user")
    }
}

fun main() {
    // Start Koin
    startKoin {
        modules(coreModule, dataModule, serviceModule)
    }

    // Run app
    val app = Application()
    app.run()

    // Parameterized factory
    val koin = org.koin.core.context.GlobalContext.get()
    val client = koin.get<ApiClient> { parametersOf("https://api.example.com") }
    println(client.get("/users"))

    // Named instances
    val primary = koin.get<Map<String, String>>(named("primary"))
    println("Primary: $primary")

    stopKoin()
}

Use Cases

  • Application dependency management
  • Testable service architecture
  • Modular application composition

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.