Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Navigating Dependency Injection with Metro

Avatar for Zac Sweers Zac Sweers
September 11, 2025

Navigating Dependency Injection with Metro

Metro is yet another compile-time dependency injection framework. How is this one different though? In this talk we’ll explore how it balances blazing-fast compile times without sacrificing features, deep dive into how it works under the hood, and how to start using it seamlessly in your existing projects. From compiler plugins to graph theory to multiplatform, this talk will cover a bit of everything!

Avatar for Zac Sweers

Zac Sweers

September 11, 2025
Tweet

More Decks by Zac Sweers

Other Decks in Programming

Transcript

  1. TODO SLIDES URL Formerly @ Slack, Uber, Flipboard Author of

    "Dagger Party Tricks" blogs/talks Past contributor to Anvil, author of Anvil-KSP, a few compiler plugins Led multiple Slack OSS projects like Circuit, compose-lints, etc Running, football, dogs 🐶
  2. What is Dependency Injection? class WeatherApp { fun run(location: String)

    { val service = WeatherService() val formatter = WeatherFormatter() } }
  3. What is Dependency Injection? class WeatherApp { fun run(location: String)

    { val service = WeatherService() val formatter = WeatherFormatter() val forecast = service.getForecast(location) println(formatter.format(forecast)) } }
  4. What is Dependency Injection? class WeatherApp { fun run(location: String)

    { val service = WeatherService() val formatter = WeatherFormatter() // ... } }
  5. What is Dependency Injection? class WeatherApp { private val service

    = WeatherService() private val formatter = WeatherFormatter() fun run(location: String) { // ... } }
  6. What is Dependency Injection? class WeatherApp { private val service

    =aWeatherService() private val formatter = WeatherFormatter() fun run(location: String) { // ... } } New York 🗽 24° Tokyo 🇯🇵 25° Curaçao 🇨🇼 31°
  7. Singletons class WeatherApp { private var service = WeatherService.getInstance() inner

    class Backdoor { fun setService(service: WeatherService) = //... } }
  8. Service Locator class WeatherApp { private val service: WeatherService by

    inject() } @Test fun exampleTest() { startKoin { modules( module { single { WeatherService() } }) } // … }
  9. Manual Injection class WeatherApp( private val service: WeatherService ) @Test

    fun exampleTest() { val app = WeatherApp(WeatherService()) //... }
  10. Manual Injection class WeatherApp( private val service: WeatherService ) @Test

    fun exampleTest() { val app = WeatherApp(FakeWeatherService()) //... }
  11. Manual Injection class WeatherApp( private val service: WeatherService private val

    formatter: WeatherFormatter ) { // ... } fun main() { val service = WeatherService() val formatter = WeatherFormatter() val app = WeatherApp(service, formatter) }
  12. Manual Injection class WeatherApp( private val service: WeatherService private val

    formatter: WeatherFormatter ) { // ... } fun main() { val httpClient = HttpClient() val service = WeatherService(httpClient) val formatter = WeatherFormatter() val app = WeatherApp(service, app) }
  13. Manual Injection class WeatherApp( private val service: WeatherService private val

    formatter: WeatherFormatter ) { // ... } fun main() { val cache = Cache() val httpClient = HttpClient(cache) val service = WeatherService(httpClient) val formatter = WeatherFormatter() val app = WeatherApp(service, app) }
  14. class Cache(…) class HttpClient(private val cache: Cache) class WeatherService(private val

    httpClient: HttpClient) class WeatherFormatter(…) class WeatherApp( private val service: WeatherService private val formatter: WeatherFormatter ) fun main() { val cache = Cache() val httpClient = HttpClient(cache) val service = WeatherService(httpClient) val formatter = WeatherFormatter() val app = WeatherApp(service, app) } Manual Injection
  15. class Cache(…) class HttpClient(private val cache: Cache) class WeatherService(private val

    httpClient: HttpClient) class WeatherFormatter(…) class WeatherApp( private val service: WeatherService private val formatter: WeatherFormatter ) fun main() { val app = TODO() } Manual Injection
  16. Why another DI framework? Dagger-style code gen Kotlin- fi rst

    Anvil aggregation Multiplatform Compiler plugin IDE diagnostics* Metro
  17. Why another DI framework? Dagger-style code gen Kotlin- fi rst

    Anvil aggregation Multiplatform Compiler plugin IDE diagnostics* Metro moshix, RCP Circuit, RCP anvil-ksp compose-lints anvil
  18. Why another DI framework? Kotlin- fi rst Anvil aggregation Multiplatform

    Compiler plugin IDE diagnostics* Metro Dagger-style code gen moshix, RCP Circuit, RCP anvil-ksp compose-lints anvil
  19. Metro DI class Cache(…) class HttpClient(private val cache: Cache) class

    WeatherService(private val httpClient: HttpClient) class WeatherFormatter(…) class WeatherApp( private val service: WeatherService private val formatter: WeatherFormatter )
  20. Metro DI @Inject class Cache(…) @Inject class HttpClient(private val cache:

    Cache) @Inject class WeatherService(private val httpClient: HttpClient) @Inject class WeatherFormatter(…) @Inject class WeatherApp( private val service: WeatherService private val formatter: WeatherFormatter )
  21. Metro DI fun main() { val cache = Cache() val

    httpClient = HttpClient() val service = WeatherService(httpClient) val formatter = WeatherFormatter() val app = WeatherApp(service, formatter) }
  22. Metro DI @DependencyGraph interface AppGraph { val app: WeatherApp }

    fun main() { val app = createGraph<AppGraph>().app }
  23. Metro DI @DependencyGraph interface AppGraph { val app: WeatherApp }

    class HttpClient class WeatherFormatter class Cache class WeatherService
  24. Metro DI @DependencyGraph interface AppGraph { val app: WeatherApp }

    @Component interface AppGraph { val app: WeatherApp }
  25. Metro DI @DependencyGraph interface AppGraph { val app: WeatherApp }

    fun main() { val app = createGraph<AppGraph>().app }
  26. Metro DI @DependencyGraph interface AppGraph { val app: WeatherApp }

    fun main() { val app = createGraph<AppGraph>().app }
  27. Metro DI @DependencyGraph interface AppGraph { val app: WeatherApp }

    fun main() { val app = AppGraph.$$MetroGraph().app }
  28. Metro DI @DependencyGraph interface AppGraph { val app: WeatherApp }

    fun main() { val app = createGraph<AppGraph>().app }
  29. Metro DI @DependencyGraph interface AppGraph { val app: WeatherApp @DependencyGraph.Factory

    interface Factory { fun create(@Provides useMetric: Boolean) : AppGraph } } fun main() { val app = createGraph<AppGraph>().app }
  30. Metro DI @DependencyGraph interface AppGraph { val app: WeatherApp @DependencyGraph.Factory

    interface Factory { fun create(@Provides useMetric: Boolean) : AppGraph } } fun main() { val app = createGraph<AppGraph>().app }
  31. Metro DI @DependencyGraph interface AppGraph { val app: WeatherApp @DependencyGraph.Factory

    interface Factory { fun create(@Provides useMetric: Boolean) : AppGraph } } fun main() { val app = createGraphFactory<AppGraph.Factory>() .create(useMetric = true) .app }
  32. Metro DI @DependencyGraph interface AppGraph { val app: WeatherApp @Provides

    fun provideCache(fs: FileSystem) : Cache = Cache(fs) }
  33. Metro DI @DependencyGraph interface AppGraph : CacheProviders { val app:

    WeatherApp } interface CacheProviders { @Provides fun provideCache(fs: FileSystem) : Cache = Cache(fs) }
  34. Metro DI @DependencyGraph interface AppGraph { val app: WeatherApp @Provides

    fun provideCache() : Cache = Cache() @Provides fun provideFormatterCache() : Cache = Cache() }
  35. Metro DI @DependencyGraph interface AppGraph { val app: WeatherApp @Provides

    fun provideCache() : Cache = Cache() @Named("FormatterCache") @Provides fun provideFormatterCache() : Cache = Cache() }
  36. Injection class HttpClient( private val cache: Cache, private val timeout:

    Duration, ) { @Inject constructor(cache: Cache) : this(cache, 30.seconds) }
  37. Metro DI @Inject class Cache @Inject class HttpClient( private val

    cache: Cache ) @Inject class WeatherService( private val httpClient: HttpClient ) @Inject class WeatherFormatter @Inject class WeatherApp( private val service: WeatherService private val formatter: WeatherFormatter ) @DependencyGraph interface AppGraph { val app: WeatherApp } fun main() { val app = createGraph<AppGraph>().app }
  38. Scoping @SingleIn(AppScope :: class) @Inject class WeatherService( private val httpClient:

    HttpClient ) @Inject class WeatherApp( private val service: WeatherService private val formatter: WeatherFormatter ) @DependencyGraph interface AppGraph { val app: WeatherApp } Always same instance
  39. Scoping All bindings and graphs are unscoped by default Scoped

    bindings are lazily initialized with thread-safe, doubly-checked locking Scope is declared at the binding, rather than the consumer of it needing to know about it Certain binding types can’t be scoped
  40. @Binds Binding impl types as interfaces they implement Binding types

    into multibindings @Binds val Int.bind: Number @Binds @IntoSet val Int.bind: Number
  41. @Binds Binding impl types as interfaces they implement Binding types

    into multibindings Binding types as nullable variants @Binds val Int.bind: Number @Binds @IntoSet val Int.bind: Number @Binds val Int.bind: Int?
  42. @Binds Binding impl types as interfaces they implement Binding types

    into multibindings Binding types as nullable variants Binding types with different quali fi ers @Binds val Int.bind: Number @Binds @IntoSet val Int.bind: Number @Binds val Int.bind: Int? @Binds @Named("timeout") val Int.bind: Int
  43. Assisted Injection @Inject class UserRepository( @Assisted val id: String, val

    httpClient: HttpClient, val db: UserDb ) { @AssistedFactory interface Factory { fun create(id: String) : UserRepository } }
  44. Assisted Injection @Inject class UserRepository( @Assisted val id: String, val

    httpClient: HttpClient, val db: UserDb ) { @AssistedFactory interface Factory { fun create(id: String) : UserRepository } }
  45. Assisted Injection @Inject class UserRepository( @Assisted val id: String, val

    httpClient: HttpClient, val db: UserDb ) { @AssistedFactory interface Factory { fun create(id: String) : UserRepository } }
  46. Assisted Injection @Inject class WeatherApp( val factory: UserRepository.Factory ) {

    fun run() { val userRepo = factory.create("d3938c4891ae") } } @Inject class UserRepository( @Assisted val id: String, val httpClient: HttpClient, val db: UserDb ) { @AssistedFactory interface Factory { fun create(id: String) : UserRepository } }
  47. Multibindings @DependencyGraph interface AppGraph { val interceptors: Set<Interceptor> val eventHandlers:

    Map<String, EventHandler> @Provide @IntoSet fun provideLoggingInterceptor() : Interceptor = LoggingInterceptor() }
  48. Multibindings @DependencyGraph interface AppGraph { val interceptors: Set<Interceptor> val eventHandlers:

    Map<String, EventHandler> @Provide @IntoSet fun provideLoggingInterceptor() : Interceptor = LoggingInterceptor() } @Inject @IntoMap @StringKey("ping") class PingHandler : EventHandler
  49. Multibindings @DependencyGraph interface AppGraph { @Multibinds(allowEmpty = true) val interceptors:

    Set<Interceptor> @Multibinds(allowEmpty = true) val eventHandlers: Map<String, EventHandler> }
  50. What's a Contribution? @DependencyGraph(AppScope :: class) interface AppGraph { val

    cache: Cache @Binds val CacheImpl.bind: Cache } @Inject class CacheImpl(…) : Cache
  51. What's a Contribution? @DependencyGraph(AppScope :: class) interface AppGraph { val

    cache: Cache } @ContributesBinding(AppScope :: class) @Inject class CacheImpl(…) : Cache
  52. What's a Contribution? @DependencyGraph(AppScope :: class) interface AppGraph : CacheImpl.AppScope$$Contribution

    { val cache: Cache } @ContributesBinding(AppScope :: class) @Inject class CacheImpl(…) : Cache { @MetroContribution interface AppScope$$Contribution { @Binds val CacheImpl.bind: Cache } }
  53. What's a Contribution? @DependencyGraph(AppScope :: class) interface AppGraph { val

    cache: Cache } @ContributesBinding(AppScope :: class) @Inject class CacheImpl(…) : Cache
  54. What's a Contribution? @DependencyGraph(AppScope :: class) interface AppGraph { val

    cache: Cache } @ContributesBinding(AppScope :: class) @Inject class CacheImpl(…) : Cache, Closeable
  55. What's a Contribution? @DependencyGraph(AppScope :: class) interface AppGraph { val

    cache: Cache } @ContributesBinding(AppScope :: class, binding = binding<Cache>()) @Inject class CacheImpl(…) : Cache, Closeable
  56. What's a Contribution? @DependencyGraph(AppScope :: class) interface AppGraph { val

    cache: Cache<Forecast> } @ContributesBinding(AppScope :: class, binding = binding<Cache<Forecast >> ()) @Inject class CacheImpl(…) : Cache<Forecast>, Closeable
  57. What's a Contribution? @DependencyGraph(AppScope :: class) interface AppGraph { val

    cache: Cache } @ContributesBinding(AppScope :: class) @Inject class CacheImpl(…) : Cache
  58. What's a Contribution? @DependencyGraph(AppScope :: class) interface AppGraph { val

    interceptors: Set<Interceptor> } @ContributesIntoSet(AppScope :: class) @Inject class LogcatInterceptor(…) : Interceptor
  59. What's a Contribution? @DependencyGraph(AppScope :: class) interface AppGraph { val

    eventHandlers: Map<String, EventHandler> } @Inject @ContributesIntoMap(AppScope :: class) @StringKey("ping") class PingHandler : EventHandler
  60. What's a Contribution? @DependencyGraph(AppScope :: class) interface AppGraph @ContributesTo(AppScope ::

    class) interface Accessors { val eventHandlers: Map<String, EventHandler> }
  61. What's a Contribution? @DependencyGraph(AppScope :: class) interface AppGraph : Accessors

    @ContributesTo(AppScope :: class) interface Accessors { val eventHandlers: Map<String, EventHandler> }
  62. @DependencyGraph(AppScope :: class) interface AppGraph { val cache: Cache val

    interceptors: Set<Interceptor> val eventHandlers: Map<String, EventHandler> } @ContributesIntoSet(AppScope :: class) @Inject class LogcatInterceptor(…) : Interceptor @Inject @ContributesIntoMap(AppScope :: class) @StringKey("ping") class PingHandler : EventHandler @ContributesBinding(AppScope :: class) @Inject class CacheImpl(…) : Cache :lib - network :app :lib - events :lib - cache
  63. Graph Extensions @GraphExtension interface LoggedInGraph { val user: User }

    @DependencyGraph interface AppGraph { val loggedInGraph: LoggedInGraph }
  64. Graph Extensions Factories that you can declare instead @GraphExtension interface

    LoggedInGraph { val user: User } @DependencyGraph interface AppGraph { val loggedInGraph: LoggedInGraph }
  65. Graph Extensions Factories that you can declare instead @GraphExtension interface

    LoggedInGraph { val user: User @GraphExtension.Factory interface Factory { fun create(@Provides id: String) : LoggedInGraph } } @DependencyGraph interface AppGraph { val loggedInGraphFactory: LoggedInGraph.Factory }
  66. Graph Extensions Factories that you can declare instead @GraphExtension interface

    LoggedInGraph { val user: User @GraphExtension.Factory interface Factory { fun createLoggedInGraph() : LoggedInGraph } } @DependencyGraph interface AppGraph : LoggedInGraph.Factory / / usage val loggedInGraph = appGraph.createLoggedInGraph()
  67. Graph Extensions Factories that you can declare instead Scope annotations

    @GraphExtension @SingleIn(LoggedInScope :: class) interface LoggedInGraph { val user: User }
  68. Graph Extensions Factories that you can declare instead Scope annotations

    Aggregation scopes @GraphExtension(LoggedInScope :: class) interface LoggedInGraph { val user: User }
  69. Graph Extensions Factories that you can declare instead Scope annotations

    Aggregation scopes Provider/Binds callables @GraphExtension(LoggedInScope :: class) interface LoggedInGraph { val user: User @Provides fun provideUser() : User = … }
  70. Graph Extensions Factories that you can declare instead Scope annotations

    Aggregation scopes Provider/Binds callables Their own extensions! @GraphExtension interface LoggedInActivityGraph { val user: User val activity: Activity } @GraphExtension interface LoggedInGraph { val user: User val loggedInActivityGraph: LoggedInActivityGraph } @DependencyGraph interface AppGraph { val loggedInGraph: LoggedInGraph }
  71. Simple Build Pipeline @DependencyGraph interface AppGraph { val app: WeatherApp

    } kotlinc (Frontend and Backend) compileKotlin .class f i les
  72. Multiplatform Build Pipeline @DependencyGraph interface AppGraph { val app: WeatherApp

    } kotlinc (Frontend and Backend) compileKotlin .class .js .wasm ...
  73. KSP Build Pipeline @DependencyGraph interface AppGraph { val app: WeatherApp

    } <compiled binaries> kotlinc (Frontend only) KSP source f i les kotlinc (Frontend and Backend) compileKotlin
  74. kotlinc (Frontend only) Stub gen .java f i les Kapt

    (javac) Kapt source f i les kotlinc (Frontend and Backend) compileKotlin .class f i les KAPT Build Pipeline
  75. kotlinc (Frontend only) Stub gen .java f i les Kapt

    (javac) Kapt source f i les kotlinc (Frontend and Backend) compileKotlin .class f i les javac compileJava More .class f i les KAPT Build Pipeline
  76. kotlinc (Frontend only) Stub gen .java f i les Kapt

    (javac) Kapt source f i les kotlinc (Frontend and Backend) compileKotlin .class f i les javac compileJava More .class f i les HILT Build Pipeline javac :hiltJavaCompileDebug Even more .class f i les
  77. kotlinc (Frontend only) Stub gen .java f i les Kapt

    (javac) Kapt source f i les kotlinc (Frontend and Backend) compileKotlin .class f i les javac compileJava More .class f i les HILT Build Pipeline javac :hiltJavaCompileDebug Even more .class f i les
  78. Metro Build Pipeline @DependencyGraph interface AppGraph { val app: WeatherApp

    } kotlinc (Frontend and Backend) compileKotlin binary f i les
  79. Metro Build Pipeline @DependencyGraph interface AppGraph { val app: WeatherApp

    } kotlinc (Frontend and Backend) compileKotlin binary f i les
  80. Metro Build Pipeline binary f i les Frontend Backend FIR

    IR Lowering Parse/PSI Source f i les
  81. Metro Build Pipeline binary f i les Frontend Backend FIR

    IR Lowering Parse/PSI fi r2ir Source f i les
  82. Declaration Generation Constructor-injected classes/providers @Inject class Cache { // Generated

    by Metro class $$MetroFactory : Factory<Cache> { constructor() companion object { fun create() : $$MetroFactory fun newInstance() : Cache } } }
  83. Declaration Generation Constructor-injected classes/providers Assisted factory impls @AssistedFactory interface Factory

    { fun create(id: String) : UserRepository // Generated by Metro class $$Impl : Factory }
  84. Declaration Generation Constructor-injected classes/providers Assisted factory impls Contribution interfaces @ContributesBinding(AppScope

    :: class) @Inject class CacheImpl(…) : Cache { // Generated by Metro @MetroContribution interface AppScope$$Contribution }
  85. Declaration Generation @DependencyGraph.Factory interface Factory { fun create() : AppGraph

    // Generated in FIR class $$Impl : Factory { constructor() } }
  86. Declaration Generation @DependencyGraph.Factory interface Factory { fun create() : AppGraph

    // Generated in FIR class $$Impl : Factory { constructor() } } @DependencyGraph.Factory interface Factory { fun create() : AppGraph class $$Impl : Factory { constructor() { } // Implemented in IR override fun create() : AppGraph = AppGraph.$$MetroGraph() } }
  87. Supertype Generation @DependencyGraph(AppScope :: class) interface AppGraph @ContributesBinding(AppScope :: class)

    @Inject class CacheImpl(…) : Cache { // Generated by Metro @MetroContribution interface AppScope$$Contribution } @ContributesTo(AppScope :: class) interface NetworkProviders { // ... }
  88. Checkers Has a single abstract function That function returns a

    dependency graph That function has valid parameters Nearly 100 more! object DependencyGraphCreatorChecker
  89. Metro Build Pipeline binary f i les Frontend Backend FIR

    IR Lowering Parse/PSI fi r2ir Source f i les
  90. Metro IR Pipeline 1. Transform non-graph classes as needed 2.

    Transform dependency graphs A. Collect graph data B. Process extensions (go to step 2) C. Build a BindingGraph D. Seal E. (If seal successful) implement the IR graph • Populate bindings • Hybrid Tarjan validation + Kahn sorting • Return result of top sorted, reachable, and deferred keys
  91. @Inject @SingleIn(AppScope : : class) class WeatherService( httpClient: HttpClient )

    @Inject class WeatherFormatter(…) @Inject class WeatherApp( service: WeatherService formatter: WeatherFormatter ) @DependencyGraph @SingleIn(AppScope : : class) interface AppGraph { val app: WeatherApp @Provides fun provideCache() : Cache = Cache() @Provides fun provideHttpClient(cache: Cache) : HttpClient = HttpClient(cache) } Metro IR Pipeline
  92. Metro IR Pipeline @DependencyGraph @SingleIn(AppScope :: class) interface AppGraph {

    val app: WeatherApp @Provides fun provideCache() : Cache = Cache() @Provides fun provideHttpClient(cache: Cache) : HttpClient = HttpClient(cache) }
  93. Metro IR Pipeline Scope — @SingleIn(AppScope :: class) @DependencyGraph interface

    AppGraph { val app: WeatherApp @Provides fun provideCache() : Cache = Cache() @Provides fun provideHttpClient( cache: Cache ) : HttpClient = HttpClient(cache) }
  94. Metro IR Pipeline Scope — @SingleIn(AppScope :: class) IrBinding.Provided —

    Cache @DependencyGraph interface AppGraph { val app: WeatherApp @Provides fun provideHttpClient( cache: Cache ) : HttpClient = HttpClient(cache) }
  95. Metro IR Pipeline Scope — @SingleIn(AppScope :: class) IrBinding.Provided —

    Cache IrBinding.Provided — HttpClient @DependencyGraph interface AppGraph { val app: WeatherApp }
  96. Metro IR Pipeline Scope — @SingleIn(AppScope :: class) IrBinding.Provided —

    Cache IrBinding.Provided — HttpClient Accessor — app: WeatherApp @DependencyGraph interface AppGraph
  97. Metro IR Pipeline Scope — @SingleIn(AppScope :: class) IrBinding.Provided —

    Cache IrBinding.Provided — HttpClient Accessor — app: WeatherApp AppGraph
  98. Metro IR Pipeline Scope — @SingleIn(AppScope :: class) Accessor —

    app: WeatherApp AppGraph BindingLookup Cache: IrBinding.Provided HttpClient: IrBinding.Provided
  99. Metro IR Pipeline WeatherApp class HttpClient class WeatherFormatter class Cache

    class WeatherService BindingLookup Cache: IrBinding.Provided HttpClient: IrBinding.Provided
  100. Metro IR Pipeline Kahn's Topo Sort WeatherApp (IrBinding.ConstructorInjected) WeatherFormatter (IrBinding.ConstructorInjected)

    WeatherService (IrBinding.ConstructorInjected) Cache (IrBinding.Provided) HttpClient (IrBinding.Provided)
  101. Metro IR Pipeline Kahn's Topo Sort Cache (IrBinding.Provided) HttpClient (IrBinding.Provided)

    WeatherFormatter (IrBinding.ConstructorInjected) WeatherService (IrBinding.ConstructorInjected) WeatherApp (IrBinding.ConstructorInjected)
  102. Metro IR Pipeline @DependencyGraph @SingleIn(AppScope :: class) interface AppGraph {

    val app: WeatherApp } Cache (IrBinding.Provided) HttpClient (IrBinding.Provided) WeatherFormatter (IrBinding.ConstructorInjected) WeatherService (IrBinding.ConstructorInjected) WeatherApp (IrBinding.ConstructorInjected)
  103. Metro IR Pipeline class $$MetroGraph : AppGraph { } Cache

    (IrBinding.Provided) HttpClient (IrBinding.Provided) WeatherFormatter (IrBinding.ConstructorInjected) WeatherService (IrBinding.ConstructorInjected) WeatherApp (IrBinding.ConstructorInjected)
  104. Metro IR Pipeline class $$MetroGraph : AppGraph { } ProviderFieldCollector

    Cache (IrBinding.Provided) HttpClient (IrBinding.Provided) WeatherFormatter (IrBinding.ConstructorInjected) WeatherService (IrBinding.ConstructorInjected) WeatherApp (IrBinding.ConstructorInjected)
  105. Metro IR Pipeline Cache (IrBinding.Provided) HttpClient (IrBinding.Provided) WeatherFormatter (IrBinding.ConstructorInjected) WeatherApp

    (IrBinding.ConstructorInjected) class $$MetroGraph : AppGraph { private val weatherServiceProvider: Provider<WeatherService> }
  106. Metro IR Pipeline WeatherFormatter (IrBinding.ConstructorInjected) WeatherApp (IrBinding.ConstructorInjected) class $$MetroGraph :

    AppGraph { private val weatherServiceProvider: Provider<WeatherService> = DoubleCheck.provider(…) }
  107. Metro IR Pipeline WeatherFormatter (IrBinding.ConstructorInjected) WeatherApp (IrBinding.ConstructorInjected) class $$MetroGraph :

    AppGraph { private val weatherServiceProvider: Provider<WeatherService> = DoubleCheck.provider( WeatherService.$$MetroFactory.create( $$HttpClientProvider.create( $$CacheProvider() ) ) ) }
  108. Metro IR Pipeline class $$MetroGraph : AppGraph { private val

    weatherServiceProvider: Provider<WeatherService> = DoubleCheck.provider( WeatherService.$$MetroFactory.create( $$HttpClientProvider.create( $$CacheProvider() ) ) ) override val app: WeatherApp get() = WeatherApp$$MetroFactory.create( this.weatherServiceProvider, WeatherFormatter$$MetroFactory.create() ).invoke() }
  109. Metro IR Pipeline class $$MetroGraph : AppGraph { private val

    weatherServiceProvider: Provider<WeatherService> = DoubleCheck.provider( WeatherService.$$MetroFactory.create( $$HttpClientProvider.create( $$CacheProvider() ) ) ) override val app: WeatherApp get() = WeatherApp$$MetroFactory.create( this.weatherServiceProvider, WeatherFormatter$$MetroFactory.create() ).invoke() }
  110. Other Stuff • Incremental compilation • Transform createGraph() and createGraphFactory()

    calls val graph = createGraph<AppGraph>() val graph = AppGraph.$$MetroGraph()
  111. Advanced Usage Binding containers @Includes Member injection Valid cycles Optional

    Top-level function injection Contribution replacements/excludes Compose integration Reports Circuit
  112. Component Interop @Component interface StartupComponent { val httpClient: HttpClient //...

    } @DependencyGraph interface AppGraph { @DependencyGraph.Factory interface Factory { fun create( @Includes startupComponent: StartupComponent ) : AppGraph } } https://zacsweers.github.io/metro/latest/adoption.html
  113. Annotations Interop @Component interface StartupComponent { val httpClient: HttpClient }

    class HttpClient @Inject constructor( val cache: Provider<Cache> ) metro { interop { enableDagger() enableAnvil() } } https://zacsweers.github.io/metro/latest/adoption.html
  114. Annotations Interop @Component interface StartupComponent { val httpClient: HttpClient }

    class HttpClient @Inject constructor( val cache: Provider<Cache> )
  115. Annotations Interop @dagger.Component interface StartupComponent { val httpClient: HttpClient }

    class HttpClient @jakarta.inject.Inject constructor( val cache: jakarta.inject.Provider<Cache> )
  116. FAQ Are compiler plugins safe to use? Solo maintainer? SPI

    / diagnostics? Debugging? Fast init IDE support/IDE plugin Dynamic/adhoc graphs Kotlin 2.3.0 support/known caveats Future