In the fast-paced world of Android development, striking the perfect balance between efficiency and...
Koin 4.1 is Here: Safer Configs, Smarter Scopes & Kotlin Multiplatform Power
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
1. Core & Configuration
Reusable Config Objects
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.
Pluggable Resolver Engine
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.
Feature Flags
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.
WASM UUID Support
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.
2. Scoping
Archetype-Based Scopes
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.
Simplified ViewModel Scopes
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.
Scope Constructor Injection for ViewModels
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.
Custom Archetypes
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.
3. Safer Module Configuration
Unified Module + Lazy Module DSL
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 Graph
With 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.
Core Annotations for Verification
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.
4. Compose & Multiplatform
-
Compose 1.8 / Lifecycle 2.9 support in
koin-compose
and MPP. -
Automatic context management: once you call
startKoin
, your Composables can simply doval 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()
andsharedViewModel()
work seamlessly with Jetpack Navigation.
Why it helps:
-
Zero friction when updating Compose or targeting multiple platforms.
-
Faster UI iteration and safer state sharing.
5. Ktor Integration
Inline Modules in 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.
Request Scoping
requestScope {
scopedOf(::RequestHandler)
}
Why it helps:
-
Co-locate your routes and DI wiring for clearer, maintainable server code.
Request Scoping
requestScope {
scopedOf(::RequestHandler)
}
Why it helps:
-
Fresh instances per HTTP call without manual context passing or attributes.
Multiplatform Artifact & Upcoming Ktor 3.2 Support
-
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.
Upgrade Checklist
-
Bump your Koin dependencies to 4.1.0
-
Review deprecations at https://insert-koin.io/docs/support/releases
-
Bundle your graph with
moduleConfiguration
and runconfig.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!