Early this year we laid out our Koin Framework 2025 Roadmap, promising that Koin 4.1 would land this year with safer configurations, stronger integrations, and multiplatform polish.
Today, Koin 4.1 is here, and it delivers exactly that—plus a few handy extras you didn’t know you needed.
In a nutshell:
Koin 4.1 gives you reusable configuration blocks, a modular resolver engine, runtime feature flags, WASM-safe UUIDs, archetype-based scopes (including ergonomic ViewModel constructor injection), a unified module/lazy-module DSL with graph verification, first-class Compose 1.8/MPP support (automatic context handling, faster injects, previews), and out-of-the-box Ktor 3.2 integration (inline modules, request scopes, multiplatform artifact).
Here's the full Koin 4.1.0 Changelog
What you can do: Pull common DI setup into a variable and mix in platform-specific calls via includes()
.
fun initKoin(common: KoinAppDeclaration? = null) {
startKoin {
includes(common)
modules(appModule)
}
}
class MainApp : Application() {
override fun onCreate() {
super.onCreate()
initKoin {
androidContext(this@MainApp)
androidLogger()
}
}
}
Why it helps:
DRY across JVM, Android, iOS, WASM.
Swap in test-only settings without rewriting your startKoin
block.
What you can do: Tie custom resolution logic into Koin via CoreResolver
and ResolutionExtension
.
startKoin {
// you can add extensions that modify lookup behavior
}
Why it helps:
Integrate smoothly with other frameworks (like Ktor’s upcoming default DI).
Cleaner, more reliable scope traversal—fewer “missing definition” edge cases.
What you can do: Enable or disable experimental behaviors at runtime.
startKoin {
options(viewModelScopeFactory())
}
Why it helps:
Roll out new features incrementally.
Turn off a flag immediately if something isn’t working in production.
Koin now uses Kotlin 2.1.20’s UUID generator under the hood for WebAssembly targets.
Why it helps:
Collision-free IDs in the browser or JS runtimes.
Safe scoping on Kotlin/WASM without extra work.
What you can do: Define scopes by “archetype” (Activity, Fragment, ViewModel, or your own).
module {
activityScope {
scopedOf(::Session)
}
fragmentScope {
// …
}
}
Then in any Activity:
class MyActivity : AppCompatActivity(), AndroidScopeComponent {
override val scope: Scope by activityScope()
}
Why it helps:
Declare lifecycle-based scopes once, reuse everywhere.
Module definitions read by intent (Activity) instead of concrete types.
What you can do: Drop the old ScopeViewModel
base class. Just implement KoinScopeComponent
.
class MyViewModel : ViewModel(), KoinScopeComponent {
override val scope = viewModelScope()
val session by inject<Session>()
}
Why it helps:
Lifecycle-driven scope open/close with no manual callbacks.
Straightforward field injection when constructor injection isn’t practical.
Flip on the viewModelScopeFactory()
option and declare your ViewModel like any other class:
startKoin {
options(viewModelScopeFactory())
}
module {
viewModelOf(::MyViewModel)
viewModelScope {
scopedOf(::Session)
}
}
Then in your activity:
class MyActivity : AppCompatActivity() {
val myVM: MyViewModel by viewModel()
}
Why it helps:
Dependencies appear in the constructor, not hidden in fields.
Better type safety and easier refactoring/testing.
You can also define your own archetype if your app has a unique lifecycle:
fun Module.wizardScope(setup: ScopeDSL.() -> Unit) =
ScopeDSL(WizardScopeArchetype, this).apply(setup)
fun ComponentActivity.createWizardScope() =
getKoin().createScope(getScopeId(),getScopeName(),this, WizardScopeArchetype)
Why it helps:
Fully tailor scope lifecycles to your app’s architecture.
Keep your DI clean even for non-standard flows.
Combine eager and lazy definitions under one roof:
val appModule = module { single { A() } }
val lazyModule = lazyModule { single { B(get()) } }
val config = moduleConfiguration {
modules(appModule)
lazyModules(lazyModule)
}
startKoin {
moduleConfiguration(config)
}
Why it helps:
One cohesive view of your entire graph.
No more juggling separate lists for lazy vs. eager modules.
verify()
Your GraphWith koin-test-coroutines
, run:
config.verify()
To use default Android types i.e koin-android-test
:
config.verify(extraTypes = androidTypes)
Why it helps:
Catch missing or mismatched bindings at test time instead of runtime.
Whitelist Android stubs so your CI tests never blow up.
Annotate constructor parameters to enrich verify()
checks:
class B(@InjectedParam val a: A)
class BProvided(@Provided val a: A)
Why it helps:
Differentiate required vs. provided params.
Make your configuration contracts explicit and testable.
Compose 1.8 / Lifecycle 2.9 support in koin-compose
and MPP.
Automatic context management: once you call startKoin
, your Composables can simply do val repo: Repo by inject()
.
Improved performance: fewer context lookups on recomposition.
Previews with mocks: use KoinApplicationPreview
to render Composables with real or fake modules.
Experimental MPP entry point: KoinMultiplatformApplication
.
Shared ViewModels: koinActivityViewModel()
and sharedViewModel()
work seamlessly with Jetpack Navigation.
Why it helps:
Zero friction when updating Compose or targeting multiple platforms.
Faster UI iteration and safer state sharing.
Application
fun Application.customerModule() {
koinModule {
singleOf(::CustomerRepoImpl) bind CustomerRepo::class
}
}
Why it helps:
Co-locate your routes and DI wiring for clearer, maintainable server code.
requestScope {
scopedOf(::RequestHandler)
}
Why it helps:
Co-locate your routes and DI wiring for clearer, maintainable server code.
requestScope {
scopedOf(::RequestHandler)
}
Why it helps:
Fresh instances per HTTP call without manual context passing or attributes.
koin-ktor
is now multiplatform.
Koin 4.1.1 will include KtorDIExtension
for Ktor’s default DI engine.
Why it helps:
Use one DI setup across JVM, native, or JS backends.
Seamless upgrade path when Ktor’s built-in DI arrives.
Bump your Koin dependencies to 4.1.0
Review deprecations at https://insert-koin.io/docs/support/releases
Bundle your graph with moduleConfiguration
and run config.verify()
in tests
Enable experimental flags (viewModelScopeFactory()
, etc.) as you validate them
Every update in 4.1 is designed to cut boilerplate, catch issues early, and give you a smooth, scalable DI foundation—just like we promised in the 2025 roadmap. Enjoy!