Skip to content

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 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.

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

  1. Bump your Koin dependencies to 4.1.0

  2. Review deprecations at https://insert-koin.io/docs/support/releases

  3. Bundle your graph with moduleConfiguration and run config.verify() in tests

  4. 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!