This project demonstrates how several dependency injection "use cases" can be supported by both Metro and Anvil+Dagger configurations. By refactoring to these common use cases, migrations between DI frameworks can happen gradually rather than in a single large change.
The project includes a DiConventionPlugin which allows toggling between "AnvilDagger" or "Metro" as the DI implementation for the entire project.
Dependency Graph (✅)
@SingleIn(AppScope::class)
@MergeComponent(AppScope::class)
interface AppGraphContributed Module (✅)
All modules in Metro must be interfaces, that means all bindings must be provided in companion objects. Only modules using ContribtuesTo are compatible with both AnvilDagger and Metro.
@ContributesTo(AppScope::class)
@Module
interface MyModuleProvidedTypeModule {
@Binds fun bindMyModuleProvidedType(real: RealMyModuleProvidedType): MyModuleProvidedType
companion object {
@Provides
@SingleIn(AppScope::class)
fun provideMyModuleProvidedType(): RealMyModuleProvidedType = RealMyModuleProvidedType()
}
}
interface MyModuleProvidedType
class RealMyModuleProvidedType : MyModuleProvidedTypeMultibinding Contributions (✅)
@ForScope(AppScope::class)
@SingleIn(AppScope::class)
@ContributesMultibinding(AppScope::class, boundType = Scoped::class)
@ContributesBinding(
AppScope::class,
boundType = UseCaseSpecificInterface::class,
ignoreQualifier = true,
)
class ScopedUseCase @Inject constructor() : Scoped, UseCaseSpecificInterfaceDagger Generated Factory (✅)
Metro is capable of using factories dagger generates for java inject constructors. Prevents all java files from having to be converted to kotlin.
@SingleIn(scope = AppScope.class)
public class MyJavaType {
@Inject public MyJavaType() {
}
}Feature with Graph (⚠️ )
Interop for ContributesSubcomponent is not yet complete: ZacSweers/metro#704.
However in the interim it is possible to double annotate contributed components AND their Factory using
both Metro and Anvil annotations. Note in order for this to work you will have to configure includeAnvil
interop manually. See convention plugin for further details.
annotation class ContributedFeatureScope
@SingleIn(ContributedFeatureScope::class)
@ContributesSubcomponent(
scope = ContributedFeatureScope::class,
parentScope = LoggedInScope::class,
)
@ContributesGraphExtension(ContributedFeatureScope::class)
interface ContributedFeatureGraph {
@ContributesSubcomponent.Factory
@ContributesGraphExtension.Factory(AppScope::class)
interface Factory {
fun create(): ContributedFeatureGraph
}
}Inject Replaced Contribution (✅)
interface BindingToReplace
@ContributesBinding(AppScope::class)
class RealBindingToReplace @Inject constructor() : BindingToReplace
@ContributesBinding(AppScope::class, replaces = [RealBindingToReplace::class])
class ReplacementBinding @Inject constructor(
private val bindingToReplace: RealBindingToReplace,
) : BindingToReplace Included Modules (✅)
class IncludedModuleProvidedType()
@Module
object IncludedObjectModule {
@Provides
@SingleIn(AppScope::class)
fun provideIncludedModuleType(): IncludedModuleProvidedType = IncludedModuleProvidedType()
}
@MergeComponent(AppScope::class, modules = [IncludedObjectModule::class])
interface AppGraphTests can be run with either DI implementation:
./gradlew :app:test -Pmad.di="AnvilDagger"
./gradlew :app:test -Pmad.di="Metro"