Skip to content

japplin/mad-interop

Repository files navigation

Metro ↔ Anvil+Dagger Interop

Overview

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.

Use Cases

Dependency Graph (✅)
@SingleIn(AppScope::class)
@MergeComponent(AppScope::class)
interface AppGraph
Contributed 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 : MyModuleProvidedType
Multibinding 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, UseCaseSpecificInterface
Dagger 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 AppGraph

Running Tests

Tests can be run with either DI implementation:

./gradlew :app:test -Pmad.di="AnvilDagger"

./gradlew :app:test -Pmad.di="Metro"

About

Metro <-> Anvil + Dagger Interop

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published