Skip to main content

KMP Without Compose

Use Anchor DI in a Kotlin Multiplatform project that does not use Compose for UI — for example, SwiftUI on iOS, Views on Android, or shared logic only. This page explains what works, what's different, and what to add. You don't need anchor-di-compose; you'll use Anchor.inject<T>() and manual scope management instead of anchorInject() and viewModelAnchor().


Dependencies

Add these dependencies to your shared module:

kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.12345debdut:anchor-di-api:x.x.x")
implementation("io.github.12345debdut:anchor-di-core:x.x.x")
implementation("io.github.12345debdut:anchor-di-presentation:x.x.x") // Optional: NavigationScopeRegistry
implementation("io.github.12345debdut:anchor-di-android:x.x.x") // Optional: ActivityScope (Android)
}
}
}

dependencies {
add("kspCommonMainMetadata", "io.github.12345debdut:anchor-di-ksp:x.x.x")
add("kspAndroid", "io.github.12345debdut:anchor-di-ksp:x.x.x")
add("kspIosArm64", "io.github.12345debdut:anchor-di-ksp:x.x.x")
add("kspIosSimulatorArm64", "io.github.12345debdut:anchor-di-ksp:x.x.x")
}

What each does:

  • anchor-di-api + anchor-di-core — Core DI. Always required.
  • anchor-di-presentationNavigationScopeRegistry for navigation-scoped DI without Compose.
  • anchor-di-androidActivityScope and Android-specific helpers.

What Works

FeatureUsage
Constructor injection@Inject, @Module, @Provides, @Binds, @InstallIn(SingletonComponent::class)
Singleton scopeAnchor.inject<T>() from root
Custom scopesAnchor.withScope(MyScope::class) { scope -> scope.get<T>() } or Anchor.scopedContainer(MyScope::class)
ViewModel scopeViewModelScopeRegistry.getOrCreate(scopeKey) → resolve from returned container; ViewModelScopeRegistry.dispose(scopeKey) when screen is gone. On Android, use anchor-di-android's getViewModelScope(owner, lifecycle) or viewModelScope() for auto-dispose.
ActivityScope (Android)Anchor.withScope(ActivityScope::class) { ... } or Anchor.scopedContainer(ActivityScope::class)
Navigation scopeNavigationScopeRegistry.getOrCreate(scopeKey) / dispose(scopeKey)
Lazy / ProviderLazy<T>, AnchorProvider<T> injection
Multibinding@IntoSet, @IntoMap, Anchor.injectSet<T>(), Anchor.injectMap<V>()
KSPSame as with Compose: kspCommonMainMetadata, kspAndroid, kspIosArm64, kspIosSimulatorArm64

What's Different

With ComposeWithout Compose
anchorInject()Use Anchor.inject<T>() directly where you need the dependency (e.g. in a screen factory or service locator).
viewModelAnchor()Use Anchor.withScope(ViewModelComponent::class) { scope -> scope.get<ViewModel>() } or hold a scoped container per screen and dispose it when leaving.
NavigationScopedContent + navigationScopedInject()Use NavigationScopeRegistry.getOrCreate(scopeKey) when entering a screen and dispose(scopeKey) when leaving (e.g. in SwiftUI or native UI).

Summary

Core DI (api, core, ksp), scopes, and codegen work the same. The main difference is that you don't have Compose-specific helpers (anchorInject(), viewModelAnchor()). Use Anchor.inject<T>() and manual scope management instead. For ViewModel and navigation scopes, use anchor-di-android and anchor-di-presentation as needed. For ViewModel scope without Compose, use ViewModelScopeRegistry.getOrCreate(scopeKey) and dispose(scopeKey) when the screen is gone; on Android, use anchor-di-android's getViewModelScope(owner, lifecycle) or viewModelScope() for auto-dispose.


Next Steps