Koin is een DI-framework voor Kotlin dat werkt zonder codegeneratie, reflectie en annotaties. De bibliotheek gebruikt DSL om modules te beschrijven en injecteert afhankelijkheden via een lichte container met ondersteuning voor Android, Ktor en Multiplatform. Volgens de officiële Koin-documentatie biedt het framework modules, scopes en ingebouwde ondersteuning voor Jetpack Compose met minimale boilerplate.
Belangrijkste punten
Koin — DI-framework voor Kotlin, geschreven in pure taal zonder gebruik van reflectie, annotaties of codegeneratie. In tegenstelling tot Dagger Hilt, dat een annotatieprocessor en codegeneratie tijdens compilatie vereist, werkt Koin uitsluitend in runtime met behulp van een lichte DSL voor het beschrijven van modules.
Het hoofdidee van Koin is het bieden van een eenvoudige API voor het registreren en oplossen van afhankelijkheden zonder dat u complexe concepten van afhankelijkheidsgrafieken en componentenbomen hoeft te leren. De ontwikkelaar beschrijft welke klassen beschikbaar zijn voor de container, en Koin injecteert ze automatisch via de constructor of luie by inject-delegaten. Het framework is volledig compatibel met Kotlin Multiplatform, waardoor een uniforme DI-aanpak op Android, iOS en de serverzijde mogelijk is.
Volgens de Kotlin Developers Community-enquête (2025) wordt Koin gebruikt in 31% van de commerciële Android-projecten en staat het qua populariteit alleen achter Hilt (47%). De belangrijkste reden voor de keuze is de eenvoud van configuratie en het ontbreken van codegeneratie, wat de projectbouw versnelt.
Kies Koin voor middelgrote en grote projecten waar een snelle start van ontwikkeling belangrijk is, of voor Kotlin Multiplatform-oplossingen waar Hilt om architecturale redenen niet beschikbaar is.
Koin gebruikt geen reflectie en codegeneratie — alle registraties zijn gebaseerd op inline-functies met reified-typen die tijdens compilatie het concrete type in de functie-body substitueren. Dit maakt Koin een van de lichtste DI-frameworks qua uiteindelijke APK-grootte: het toevoegen van Koin vergroot de app met slechts 100–150 KB, terwijl Dagger Hilt ongeveer 500 KB toevoegt vanwege gegenereerde code.
De Koin-container wordt geïnitialiseerd via de functie startKoin die een lambda met configuratie ontvangt. Binnen deze lambda worden modules met registraties beschreven — de primaire bouwsteen van DI-logica.
De functie startKoin creëert een globale container die vanuit elk punt van de applicatie toegankelijk is via GlobalContext, maar in multi-moduleprojecten wordt aanbevolen KoinApplication te gebruiken voor het maken van geïsoleerde containers. In Android wordt voor initialisatie AndroidContext gebruikt, die automatisch wordt gekoppeld aan de levenscyclus van Application. Modules worden geregistreerd via de parameter modules, die een lijst van Module-instanties accepteert.
val networkModule = module {
single {
OkHttpClient()
}
single {
Retrofit.Builder()
.baseUrl("https://api.example.com")
.build()
}
}
startKoin {
modules(networkModule)
}
Elke module bevat definities via single (singleton) of factory (nieuwe instantie). Definities kunnen verwijzen naar andere geregistreerde afhankelijkheden via get(), wat een injectiegrafiek vormt zonder expliciete type-aanduiding en zonder boilerplate-code.
Koin maakt actief gebruik van inline-functies met reified-parameters voor type-afleiding uit de context. Dit maakt het mogelijk registraties te schrijven zonder expliciete klasse-aanduiding: single { MyService() } bepaalt automatisch het type op basis van de retourwaarde van de lambda.
In tegenstelling tot Dagger controleert Koin de afhankelijkheidsgraaf niet tijdens compilatie — alle fouten worden in runtime gedetecteerd bij de eerste toegang tot een onopgeloste afhankelijkheid. Dit is een compromis dat de code aanzienlijk vereenvoudigt en de bouw versnelt, maar testdekking van de DI-configuratie vereist. Veel teams kiezen Koin juist vanwege de ontwikkelingssnelheid en eenvoud, ondanks het ontbreken van compile-time-controles.
In Koin versie 3.5 is experimentele controle van de graaf tijdens compilatie verschenen via de Koin Annotations-plugin. De ontwikkelaar voegt @Module- en @KoinComponent-annotaties toe, en de plugin genereert validatiecode die tijdens de bouw wordt uitgevoerd. Het belangrijkste voordeel van Koin — het ontbreken van codegeneratie — gaat echter in deze modus verloren, daarom blijven de meeste teams de klassieke DSL-aanpak met runtime-controles via tests gebruiken.
Koin biedt verschillende manieren om afhankelijkheden te injecteren: by inject(), get() en directe overdracht via de constructor. De keuze van de methode hangt af van de gebruikscontext.
De by inject-delegaat — de meest voorkomende injectiemethode in ViewModel en Android-fragmenten. De afhankelijkheid wordt lui geïnitialiseerd — alleen bij de eerste toegang tot de eigenschap. Dit is efficiënt voor resource-intensieve services die mogelijk niet direct nodig zijn.
class MainViewModel : ViewModel() {
private val repository: UserRepository by inject()
fun loadUsers() {
repository.fetchAll()
}
}
De functie get retourneert de instantie van de afhankelijkheid onmiddellijk. Wordt gebruikt binnen fabriekslambda's bij registratie of wanneer de afhankelijkheid in een synchrone context zonder luie initialisatie nodig is. In tegenstelling tot by inject() ondersteunt get() geen lui laden en vereist het dat de container al is geïnitialiseerd op het moment van aanroep.
Scope in Koin — mechanisme om de levensduur van afhankelijkheden te koppelen aan een specifiek component, zoals Activity, Fragment of een gebruikerssessie. Dit is een cruciale functionaliteit voor geheugenbeheer in Android-applicaties.
De functie scope binnen een module creëert een scope die leeft zolang het gekoppelde component leeft. Alle in de scope geregistreerde afhankelijkheden worden vernietigd bij het sluiten ervan, wat geheugenlekken voorkomt.
val userScope = module {
scope<UserSession> {
scoped {
UserRepository(get())
}
scoped {
SessionManager(get())
}
}
}
De functie scoped registreert een afhankelijkheid die alleen binnen de scope zal bestaan. Bij het sluiten van de scope worden alle scoped-objecten ontoegankelijk voor de garbage collector.
single — registratie van een enkele instantie voor de gehele applicatie met luie initialisatie. Gebruikt voor stateless services: netwerkclients, caches, loggers.
factory — elke get()-aanroep creëert een nieuwe instantie. Toegepast voor ViewModel-klassen, repositories en objecten met status waarbij een verse instantie bij elke toegang belangrijk is.
Integratie van Koin in een Android-project is minimaal: voeg de afhankelijkheid toe in build.gradle en roep startKoin aan in Application.onCreate. Koin biedt modules voor integratie met Jetpack Compose, Navigation en WorkManager, wat het een volwaardig alternatief voor Hilt maakt.
De speciale bibliotheek koin-android-compose maakt het mogelijk afhankelijkheden rechtstreeks in Composable-functies te injecteren via de functies koinViewModel() en koinInject(). Dit elimineert de noodzaak om de container via parameters van elk scherm door te geven en maakt de ViewModel-code schoner door automatische koppeling aan de levenscyclus.
Volgens Google I/O 2024 is Jetpack Compose het belangrijkste framework geworden voor nieuwe Android-projecten. Koin biedt native Compose-ondersteuning zonder extra configuratie, koppelt automatisch scopes aan de ViewModel-levenscyclus via koinViewModel() en houdt rekening met de coroutine-context.
Voor testen biedt Koin de functies koinTest en koinTestRule, die een geïsoleerde testcontainer met testmodules maken en deze automatisch sluiten na afloop van de test. Dit zorgt voor isolatie van tests en voorkomt toestandslekken tussen testgevallen.
Integratie van Koin met Jetpack Navigation wordt gerealiseerd via de module koin-androidx-navigation. De ViewModel van elk scherm ontvangt automatisch afhankelijkheden via by viewModel() met overdracht van SavedStateHandle voor het behouden van toestand bij schermrotatie en herstel na minimalisatie van de app.
Voor het unit-testen van ViewModel met Koin wordt koinTestRule uit de bibliotheek koin-test-junit5 of koin-test-junit4 gebruikt. De regel creëert voor elke test een geïsoleerde container met testmodules en sluit deze automatisch na afloop, waardoor toestandslekken tussen testgevallen worden voorkomen. Echte afhankelijkheden worden vervangen door mocks via MockK: de module met registraties single
Een van de belangrijkste mogelijkheden van Koin 3.x is ondersteuning voor Ktor voor het maken van serverapplicaties in Kotlin en Compose Multiplatform voor desktopapplicaties. Dit maakt Koin het enige DI-framework dat alle drie Kotlin-platforms dekt zonder verandering van injectieparadigma. De module koin-ktor maakt het mogelijk afhankelijkheden te registreren via install(Koin) in het Application-blok en services in routes te injecteren via by inject(), net zoals in Android. Dit maakt Koin een universele DI-oplossing voor Kotlin-projecten van elke architectuur — van mobiele client tot serverbackend.
Integratie van Koin met Jetpack Navigation via de module koin-androidx-navigation elimineert de noodzaak om handmatig ViewModelProvider.Factory voor elk scherm te maken. Voor multi-moduleprojecten ondersteunt Koin het lui laden van modules via loadKoinModules, waardoor de DI-configuratie van elke feature-module onafhankelijk kan worden aangesloten.
Veelgestelde vragen
Koin werkt in runtime zonder codegeneratie en annotaties, wat de bouw versnelt, maar controleert de afhankelijkheidsgraaf niet tijdens compilatie. Hilt genereert code tijdens compilatie en detecteert DI-fouten eerder, maar vereist complexe configuratie en vertraagt de build.
Ja, Koin ondersteunt Kotlin Multiplatform volledig. De bibliotheek koin-core werkt op alle Kotlin-platforms, en koin-android en koin-compose voegen platformspecifieke mogelijkheden toe voor respectievelijk Android en iOS.
Cyclische afhankelijkheden leiden tot StackOverflowError in runtime. Koin detecteert ze niet automatisch. Oplossing — refactoring van de architectuur: extractie van een gemeenschappelijke interface, gebruik van het Listener/Observer-patroon of het doorbreken van de cyclus via een fabriek met uitgestelde initialisatie.
In Android kunnen scopes via AndroidScope worden gekoppeld aan de levenscyclus van Activity of Fragment. Bij vernietiging van het component sluit Koin automatisch de bijbehorende scope. In aangepaste scopes (gebruikerssessie) gebeurt het sluiten handmatig via scope.close.
Gebruik de functie koinTest uit de module koin-test. Deze creëert een geïsoleerde container met testmodules die automatisch wordt gesloten na de test. Echte afhankelijkheden worden vervangen door mocks via een module met Mockito of MockK.
Samenvatting
We ontwikkelen een mobiele applicatie turnkey
IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.
Lees ook