[CMP + KSP] Missing definition with interface defined in commonMain when androidMain has annotated impl
Describe the bug
I have an interface IInventoryManager that has an impl HttpInventoryManager both defined in commonMain of my CMP project. HttpInventoryManager has a dependency on a IHttpClientFactory that should be provided by a native source set (ie Android or iOS). In my adroidMain, I have an OkHttpClientFactory that is annotated as @Single. An expect module defined in commonMain and actual'd in androidMain which scans the package for the impl. KOIN_CONFIG_CHECK is set, and at build time, the following error is provided:
[ksp] --> Missing Definition for property 'httpClientFactory : com.bigballard.tool.http.IHttpClientFactory' in 'com.bigballard.tool.inventory.HttpInventoryManager'
To Reproduce Steps to reproduce the behavior:
- Create a CMP project with android as a target a. Setup for Koin per documentation
- Define an interface in commonMain and impl for interface in androidMain annotated with
@Single - Define an expect module class in commonMain and actual in androidMain
- Create class with constructor dependency in commonMain on interface and a annotate with
@Single - Set
KOIN_CONFIG_CHECKto true in build.gradle - Build project
Expected behavior
That the OkHttpClientFactory definition would be picked up by koin and fulfill that IHttpClientFactory dependency.
Koin project used and used version (please complete the following information):
koin-core: 4.1.0
koin-compose: 4.1.0
koin-compose-viewmodel: 4.1.0
koin-annotations: 2.1.0
koin-ksp-compiler2.1.0
Additional moduleDefinition DI.kt - commonMain
fun initDI(config: KoinAppDeclaration? = null) {
startKoin {
if (config != null) {
includes(config)
} else {
printLogger()
}
modules(
AppModule().module
)
}
}
AppModule.kt - commonMain
@Module(includes = [AuthModule::class, InventoryModule::class, HttpModule::class])
class AppModule
HttpModule.kt - commonMain
@Module
expect class HttpModule
HttpClientFactory.kt - commonMain
interface IHttpClientFactory
InventoryManager.kt - commonMain
interface IInventoryManager
@Single(binds = [IInventoryManager::class])
class HttpInventoryManager(private val httpClientFactory: IHttpClientFactory): IInventoryManager
HttpModule.android.kt - androidMain
@Module
@ComponentScan
actual class HttpModule
OkHttpClientFactory.kt - androidMain
@Single(binds = [IHttpClientFactory::class])
class OkHttpClientFactory(): IHttpClientFactory
I encountered the exact same problem, but within a more complex module hierarchy that reveals an additional aspect of the bug.
The Specific Problem: When using a chain of module inclusions: ContextModule (with expect/actual) → is included in → ModuleA ModuleA → is included in → ModuleB
The Koin Config Check behaves inconsistently: ✅ For ModuleA, the check passes successfully ❌ For ModuleB, it fails with an error about being unable to resolve ContextWrapper
It is important to note that:
- The ContextModule and ContextWrapper are implemented exactly as described in the official Koin documentation for platform wrappers.
- ModuleB does not use ContextWrapper directly in its own definitions.
- ModuleB has no dependency on the Gradle module containing the implementation of ContextWrapper.