In my current project we are slowly migrating from Dagger2 (non-Hilt) into the Koin world, module by module, and figured we need to bridge a couple of new things into the old world. But how exactly would one do that?
My solution was to let Dagger create the Koin graph as part of it’s own graph, like so:
import org.koin.core.module.Module as KoinModule
@Module
class KoinCompatModule {
@Provides
@Singleton
@Suppress("SpreadOperator")
fun provideKoinApplication(koinModules: Set<KoinModule>): KoinApplication =
startKoin {
modules(*koinModules.toTypedArray())
}
}
Now, each new Gradle module just has to provide a compatible Dagger2 module alongside the Koin module for injection:
import org.koin.core.module.Module as KoinModule
fun coreDomainModule(): KoinModule =
module {
single<ClockProvider> { ClockProviderImpl() }
single<DispatcherProvider> { DispatcherProviderImpl() }
}
@Module
class CoreDomainModule {
@Provides
@IntoSet
internal fun provideCoreDomainModule(): KoinModule = coreDomainModule()
@Provides
internal fun provideClockProvider(app: KoinApplication): ClockProvider =
app.koin.get()
@Provides
internal fun provideDispatcherProvider(app: KoinApplication): DispatcherProvider =
app.koin.get()
}
In Dagger dependencies are configured unscoped, so that the underlying KoinApplication keeps the ownership / track of instance lifetime. Finally, to have new applications sharing the same modules not depend on Dagger2, we define them as compileOnly
:
dependencies {
compileOnly(libs.dagger2.core)
ksp(libs.dagger2.compiler)
}
Then all what is missing is to add KoinCompatModule
and CoreDomainModule
to the specific Dagger component you want to have them in. Obviously all this works best with singleton / unscoped dependencies, i.e. when it comes to activity or even viewmodel-scoped dependencies this will probably not work so easily, but it’s a start 🙂