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

All aboard the Metro

All aboard the Metro

Presentation for the Paris Android User Group January 13th at chez amo.

We'll explore the newest Dependency Injection framework called Metro and how it compares to Hilt

Avatar for Jonas Pedersen

Jonas Pedersen

January 13, 2026

Other Decks in Programming

Transcript

  1. What is Metro? - Dependency Injection library for Kotlin -

    Kotlin compiler plugin - Authored by Zac Sweers (Slack, Uber) https://zacsweers.github.io/metro/latest/
  2. This presentation - Compare Hilt and Metro - Why switch?

    - How to switch? - Result of switching
  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() val forecast = service.getForecast(location) println(formatter.format(forecast)) } }
  5. What is Dependency Injection? class WeatherApp constructor( private val service:

    WeatherService, private val formatter: WeatherFormatter ) { lateinit var printer: WeatherPrinter /// ..... }
  6. What is Dependency Injection? class WeatherApp constructor( private val service:

    WeatherService, private val formatter: WeatherFormatter ) { lateinit var printer: WeatherPrinter /// ..... } fun main() { val service = WeatherService() val formatter = WeatherFormatter() val app = WeatherApp(service, formatter) app.printer = WeatherPrinter() }
  7. What is Dependency Injection? class WeatherApp constructor( private val service:

    WeatherService, private val formatter: WeatherFormatter ) { lateinit var printer: WeatherPrinter /// ..... } fun main() { val service = WeatherService() val formatter = WeatherFormatter() val app = WeatherApp(service, formatter) app.printer = WeatherPrinter() }
  8. What is Dependency Injection? class WeatherApp constructor( private val service:

    WeatherService, private val formatter: WeatherFormatter ) { lateinit var printer: WeatherPrinter /// ..... } fun main() { val service = WeatherService() val formatter = WeatherFormatter() val app = WeatherApp(service, formatter) app.printer = WeatherPrinter() }
  9. What is Dependency Injection? class WeatherApp constructor( private val service:

    WeatherService, private val formatter: WeatherFormatter ) { lateinit var printer: WeatherPrinter /// ..... } fun main() { val service = WeatherService() val formatter = WeatherFormatter() val app = WeatherApp(service, formatter) app.printer = WeatherPrinter() }
  10. What is Dependency Injection? class WeatherApp @Inject constructor( private val

    service: WeatherService, private val formatter: WeatherFormatter ) { @Inject lateinit var printer: WeatherPrinter /// ..... }
  11. Hilt - Dependency Injection library for Android - Annotation processor

    (KAPT, KSP) - Built on Dagger - Maintained and Recommended by Google
  12. Hilt -> Metro interface WeatherService class WeatherServiceImpl @Inject constructor(): WeatherService

    { // ... } @HiltAndroidApp class WeatherApplication : Application() @HiltEntryPoint(ComponentActivity::class) class WeatherActivity : ComponentActivity() {} @HiltViewModel class WeatherViewModel @Inject constructor(val service: WeatherService) : ViewModel()
  13. Hilt -> Metro interface WeatherService class WeatherServiceImpl @Inject constructor(): WeatherService

    { // ... } @HiltAndroidApp class WeatherApplication : Application() @HiltEntryPoint(ComponentActivity::class) class WeatherActivity : ComponentActivity() {} @HiltViewModel class WeatherViewModel @Inject constructor(val service: WeatherService) : ViewModel()
  14. Hilt -> Metro @DependencyGraph(AppScope::class) interface WeatherAppGraph { @Binds @ApplicationContext val

    Application.binds: Context fun inject(app: WeatherApplication) @DependencyGraph.Factory interface Factory { fun create(@Provides app: Application): WeatherAppGraph } }
  15. Hilt -> Metro @DependencyGraph(AppScope::class) interface WeatherAppGraph { @Binds @ApplicationContext val

    Application.binds: Context fun inject(app: WeatherApplication) @DependencyGraph.Factory interface Factory { fun create(@Provides app: Application): WeatherAppGraph } }
  16. Hilt -> Metro @DependencyGraph(AppScope::class) interface WeatherAppGraph { @Binds @ApplicationContext val

    Application.binds: Context fun inject(app: WeatherApplication) @DependencyGraph.Factory interface Factory { fun create(@Provides app: Application): WeatherAppGraph } }
  17. Hilt -> Metro @DependencyGraph(AppScope::class) interface WeatherAppGraph { @Binds @ApplicationContext val

    Application.binds: Context fun inject(app: WeatherApplication) @DependencyGraph.Factory interface Factory { fun create(@Provides app: Application): WeatherAppGraph } }
  18. Hilt -> Metro @DependencyGraph(AppScope::class) interface WeatherAppGraph { @Binds @ApplicationContext val

    Application.binds: Context fun inject(app: WeatherApplication) @DependencyGraph.Factory interface Factory { fun create(@Provides app: Application): WeatherAppGraph } }
  19. Hilt -> Metro class WeatherApplication : Application() { val graph

    by lazy { createGraphFactory<WeatherAppGraph.Factory>().create(this) } @Inject lateinit var logger: Logger override fun onCreate() { super.onCreate() graph.inject(this) } }
  20. Hilt -> Metro class WeatherApplication : Application() { val graph

    by lazy { createGraphFactory<WeatherAppGraph.Factory>().create(this) } @Inject lateinit var logger: Logger override fun onCreate() { super.onCreate() graph.inject(this) } }
  21. Hilt -> Metro class WeatherApplication : Application() { val graph

    by lazy { createGraphFactory<WeatherAppGraph.Factory>().create(this) } @Inject lateinit var logger: Logger override fun onCreate() { super.onCreate() graph.inject(this) } }
  22. Hilt -> Metro class WeatherActivity : ComponentActivity() { val activityRetained

    by viewModels<ActivityRetainedViewModel> { object : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { val app = applicationContext as WeatherApplication val graph = (app.graph as WeatherActivityRetainedGraph.Factory) .createWeatherActivityRetainedGraph() @Suppress("UNCHECKED_CAST") return ActivityRetainedViewModel(graph) as T } } } } class ActivityRetainedViewModel(val graph: WeatherActivityRetainedGraph) : ViewModel()
  23. Hilt -> Metro class WeatherActivity : ComponentActivity() { val activityRetained

    by viewModels<ActivityRetainedViewModel> { object : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { val app = applicationContext as WeatherApplication val graph = (app.graph as WeatherActivityRetainedGraph.Factory) .createWeatherActivityRetainedGraph() @Suppress("UNCHECKED_CAST") return ActivityRetainedViewModel(graph) as T } } } } class ActivityRetainedViewModel(val graph: WeatherActivityRetainedGraph) : ViewModel()
  24. Hilt -> Metro class WeatherActivity : ComponentActivity() { val activityRetained

    by viewModels<ActivityRetainedViewModel> { object : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { val app = applicationContext as WeatherApplication val graph = (app.graph as WeatherActivityRetainedGraph.Factory) .createWeatherActivityRetainedGraph() @Suppress("UNCHECKED_CAST") return ActivityRetainedViewModel(graph) as T } } } } class ActivityRetainedViewModel(val graph: WeatherActivityRetainedGraph) : ViewModel()
  25. Hilt -> Metro class WeatherActivity : ComponentActivity() { val activityRetained

    by viewModels<ActivityRetainedViewModel> { object : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { val app = applicationContext as WeatherApplication val graph = (app.graph as WeatherActivityRetainedGraph.Factory) .createWeatherActivityRetainedGraph() @Suppress("UNCHECKED_CAST") return ActivityRetainedViewModel(graph) as T } } } } class ActivityRetainedViewModel(val graph: WeatherActivityRetainedGraph) : ViewModel()
  26. Hilt -> Metro class WeatherActivity : ComponentActivity() { val activityRetained

    by viewModels<ActivityRetainedViewModel> { object : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { val app = applicationContext as WeatherApplication val graph = (app.graph as WeatherActivityRetainedGraph.Factory) .createWeatherActivityRetainedGraph() @Suppress("UNCHECKED_CAST") return ActivityRetainedViewModel(graph) as T } } } } class ActivityRetainedViewModel(val graph: WeatherActivityRetainedGraph) : ViewModel()
  27. Hilt -> Metro @GraphExtension(ActivityScoped::class) interface ActivityGraph { @ContributesTo(ActivityRetainedScoped::class) @GraphExtension.Factory interface

    Factory { fun createActivityGraph(): ActivityGraph } } @GraphExtension(ViewModelScoped::class) interface ViewModelGraph { @ContributesTo(ActivityRetainedScoped::class) @GraphExtension.Factory interface Factory { fun createViewModelGraph(): ViewModelGraph } }
  28. Hilt -> Metro @GraphExtension(ActivityScoped::class) interface ActivityGraph { @ContributesTo(ActivityRetainedScoped::class) @GraphExtension.Factory interface

    Factory { fun createActivityGraph(): ActivityGraph } } @GraphExtension(ViewModelScoped::class) interface ViewModelGraph { @ContributesTo(ActivityRetainedScoped::class) @GraphExtension.Factory interface Factory { fun createViewModelGraph(): ViewModelGraph } }
  29. Hilt -> Metro @GraphExtension(ActivityScoped::class) interface ActivityGraph { @ContributesTo(ActivityRetainedScoped::class) @GraphExtension.Factory interface

    Factory { fun createActivityGraph(): ActivityGraph } } @GraphExtension(ViewModelScoped::class) interface ViewModelGraph { @ContributesTo(ActivityRetainedScoped::class) @GraphExtension.Factory interface Factory { fun createViewModelGraph(): ViewModelGraph } }
  30. Hilt -> Metro // HILT @Module @InstallIn(SingletonComponent::class) interface WeatherModule {

    @Binds fun bindWeatherService(impl: WeatherServiceImpl): WeatherService } Bindings
  31. Hilt -> Metro // HILT @Module @InstallIn(SingletonComponent::class) interface WeatherModule {

    @Binds fun bindWeatherService(impl: WeatherServiceImpl): WeatherService } // Metro @BindingContainer // Optionally find/replace @Module @ContributesTo(AppScope::class) interface WeatherModule { @Binds fun bindWeatherService(impl: WeatherServiceImpl): WeatherService } Bindings
  32. Hilt -> Metro // HILT @Module @InstallIn(SingletonComponent::class) interface WeatherModule {

    @Binds fun bindWeatherService(impl: WeatherServiceImpl): WeatherService } // Metro @BindingContainer // Optionally find/replace @Module @ContributesTo(AppScope::class) interface WeatherModule { @Binds fun bindWeatherService(impl: WeatherServiceImpl): WeatherService } Bindings
  33. Hilt -> Metro // HILT @Module @InstallIn(SingletonComponent::class) interface WeatherModule {

    @Binds fun bindWeatherService(impl: WeatherServiceImpl): WeatherService } // Metro @BindingContainer // Optionally find/replace @Module @ContributesTo(AppScope::class) interface WeatherModule { @Binds fun bindWeatherService(impl: WeatherServiceImpl): WeatherService } @ContributesBinding(AppScope::class) class WeatherServiceImpl : WeatherService { Bindings
  34. Hilt -> Metro ViewModel https://zacsweers.github.io/metro/latest/metrox-viewmodel/ @Inject @ContributesBinding(AppScope::class) @SingleIn(AppScope::class) class MyViewModelFactory(

    override val viewModelProviders: Map<KClass<out ViewModel>, Provider<ViewModel>>, override val assistedFactoryProviders: Map<KClass<out ViewModel>, Provider<ViewModelAssistedFactory>>, override val manualAssistedFactoryProviders: Map<KClass<out ManualViewModelAssistedFactory>, Provider<ManualViewMo ) : MetroViewModelFactory()
  35. Hilt -> Metro ViewModel @Composable fun WeatherScreen(viewModel: WeatherViewModel = metroViewModel())

    { } @ContributesIntoMap(ViewModelScoped::class) @ViewModelKey(WeatherViewModel::class) class WeatherViewModel(val service: WeatherService) : ViewModel()
  36. Hilt -> Metro EntryPoints / Inject in compose val LocalViewGraph

    = compositionLocalOf<ViewGraph> { error("ViewGraph not set") }
  37. Hilt -> Metro EntryPoints / Inject in compose val LocalViewGraph

    = compositionLocalOf<ViewGraph> { error("ViewGraph not set") } @ContributesTo(ViewScoped::class) interface WeatherContribution { val formatter: WeatherFormatter }
  38. Hilt -> Metro EntryPoints / Inject in compose val LocalViewGraph

    = compositionLocalOf<ViewGraph> { error("ViewGraph not set") } @ContributesTo(ViewScoped::class) interface WeatherContribution { val formatter: WeatherFormatter } not a Module / BindingContainer
  39. Hilt -> Metro EntryPoints / Inject in compose val LocalViewGraph

    = compositionLocalOf<ViewGraph> { error("ViewGraph not set") } @ContributesTo(ViewScoped::class) interface WeatherContribution { val formatter: WeatherFormatter } @Composable inline fun <reified T> contribution(): T { return LocalViewGraph.current as T }
  40. Hilt -> Metro EntryPoints / Inject in compose val LocalViewGraph

    = compositionLocalOf<ViewGraph> { error("ViewGraph not set") } @ContributesTo(ViewScoped::class) interface WeatherContribution { val formatter: WeatherFormatter } @Composable inline fun <reified T> contribution(): T { return LocalViewGraph.current as T } @Composable fun WeatherScreen() { val contribution = contribution<WeatherContribution>() val formatter = remember { contribution.formatter } }
  41. Hilt -> Metro EntryPoints / Inject in compose val LocalViewGraph

    = compositionLocalOf<ViewGraph> { error("ViewGraph not set") } @ContributesTo(ViewScoped::class) interface WeatherContribution { val formatter: WeatherFormatter } @Composable inline fun <reified T> contribution(): T { return LocalViewGraph.current as T } @Composable fun WeatherScreen() { val contribution = LocalViewGraph.current.asContribution<WeatherContribution>() val formatter = remember { contribution.formatter } }
  42. What are the results? BEFORE apps: 5 components: 333 platform:

    130 foundation: 13 TOTAL: 482 AFTER apps: 5 components: 211 platform: 127 foundation: 13 TOTAL: 357 -125 modules -25% modules
  43. So why not Koin? - Static analysis is a non-negotiable

    for us - Koin-annotations uses KSP
  44. Any downsides? - Still pretty new - Some corner cases

    not found can lead to confusing error messages - Still not v1.0, API can technically still change - Android Studio support
  45. What else? - Multibindings - Optionals - Multiplatform - Top

    level function injection - Assisted injection