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

Scale your Kotlin Multiplatform projects using ...

Avatar for Ralf Ralf
May 23, 2025

Scale your Kotlin Multiplatform projects using dependency injection

Kotlin-inject-anvil is a dependency injection framework for Kotlin Multiplatform. It verifies the dependency graph at compile time and generates the necessary code to instantiate the object graph at runtime. The framework aims to provide a safe dependency injection solution without the boilerplate and configurations common in many other libraries.

This talk will introduce kotlin-inject-anvil, discussing its benefits and how it scales in large, modularized codebases. We'll explore how to leverage the strengths of each host platform while maximizing common Kotlin Multiplatform code. Since each codebase is unique, I'll demonstrate how we adapt the framework for our own internal use cases at Amazon.

Avatar for Ralf

Ralf

May 23, 2025
Tweet

More Decks by Ralf

Other Decks in Technology

Transcript

  1. Background • Adopted KMP for new initiatives in 2024 •

    Shared APIs • Shared or distinct implementations • Common architecture for shared features
  2. Background • On Android we used Dagger 2 and Anvil

    ◦ We like Dagger 2 for its safety checks and runtime speed ◦ We like Anvil for its simplicity
  3. Anvil interface WeatherRepository { fun provideForecast(location: Location): Forecast } @Singleton

    @ContributesBinding(AppScope::class) class WeatherRepositoryImpl @Inject constructor( client: HttpClient, ) : WeatherRepository
  4. Background • On Android we used Dagger 2 and Anvil

    ◦ We like Dagger 2 for its safety checks and runtime speed ◦ We like Anvil for its simplicity
  5. Background • On Android we used Dagger 2 and Anvil

    ◦ We like Dagger 2 for its safety checks and runtime speed ◦ We like Anvil for its simplicity • But both don’t work with KMP in common code
  6. Dependency Inversion Dependency inversion is a design principle to reduce

    coupling between components. It means that high-level APIs don’t depend on low-level details and low-level details only import other high-level APIs.
  7. Dependency Inversion interface WeatherRepository { fun provideForecast(location: Location): Forecast }

    class WeatherRepositoryImpl( ... ) : WeatherRepository { ... } class WeatherReport( weatherRepository: WeatherRepository, )
  8. Dependency Inversion interface WeatherRepository { fun provideForecast(location: Location): Forecast }

    class WeatherRepositoryImpl( ... ) : WeatherRepository { ... } class WeatherReport( weatherRepository: WeatherRepository, )
  9. Dependency Injection Dependency injection is a design principle where objects

    and classes receive dependencies rather than creating and managing them themselves. This principle reduces coupling and improves testability.
  10. Do we need a framework? interface WeatherRepository class WeatherRepositoryImpl :

    WeatherRepository class WeatherReport( val weatherRepository: WeatherRepository, )
  11. Do we need a framework? fun showWeatherReport() { val weatherRepository:

    WeatherRepository = WeatherRepositoryImpl() val weatherReport = WeatherReport(weatherRepository) }
  12. Do we need a framework? object Component { val weatherRepository:

    WeatherRepository = WeatherRepositoryImpl() val weatherReport = WeatherReport(weatherRepository) } fun showWeatherReport() { Component.weatherReport }
  13. Do we need a framework? interface LocationProvider class RealTimeLocationProvider :

    LocationProvider class WeatherRepositoryImpl( locationProvider: LocationProvider, ) : WeatherRepository
  14. Do we need a framework? object Component { val weatherRepository:

    WeatherRepository = WeatherRepositoryImpl() val weatherReport = WeatherReport(weatherRepository) } fun showWeatherReport() { Component.weatherReport }
  15. Do we need a framework? object Component { val locationProvider:

    LocationProvider = RealTimeLocationProvider() val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) val weatherReport = WeatherReport(weatherRepository) } fun showWeatherReport() { Component.weatherReport }
  16. Do we need a framework? object Component { val locationProvider:

    LocationProvider = RealTimeLocationProvider() val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) val weatherReport = WeatherReport(weatherRepository, locationProvider) } fun showWeatherReport() { Component.weatherReport }
  17. Problems • Replacing dependencies in tests • Scopes with shorter

    lifecycle • Adding classes we don’t own • Component needs to be updated frequently (high maintenance) • Component needs to live in the application modules (duplication) • …
  18. Service locator class Component { private val services = mutableMapOf<KClass<*>,

    Any>() fun <T : Any> register(clazz: KClass<T>, dependency: T) { services[clazz] = dependency } fun <T : Any> get(clazz: KClass<T>): T { @Suppress("UNCHECKED_CAST") return services.getValue(clazz) as T } inline fun <reified T: Any> get(): T = get(T::class) }
  19. Service locator fun main() { val component = Component() component.register(LocationProvider::class,

    RealTimeLocationProvider()) component.register(WeatherRepository::class, WeatherRepositoryImpl(component.get())) component.register(WeatherReport::class, WeatherReport(component.get(), component.get())) }
  20. Service locator fun main() { val component = Component() component.register(LocationProvider::class,

    RealTimeLocationProvider()) component.register(WeatherRepository::class, WeatherRepositoryImpl(component.get())) component.register(WeatherReport::class, WeatherReport(component.get(), component.get())) }
  21. Service locator fun main() { val component = Component().apply {

    register(LocationProvider::class, RealTimeLocationProvider()) register(WeatherRepository::class, WeatherRepositoryImpl(get())) register(WeatherReport::class, WeatherReport(get(), get())) } }
  22. Service locator fun main() { val component = Component().apply {

    register(LocationProvider::class, RealTimeLocationProvider()) register(WeatherRepository::class, WeatherRepositoryImpl(get())) register(WeatherReport::class, WeatherReport(get(), get())) } }
  23. Problems • No compile time safety • No circular dependency

    detection • Race conditions • Boilerplate • Service locator
  24. Invoke constructors class Component { fun <T : Any> get(clazz:

    KClass<T>): T { @Suppress("UNCHECKED_CAST") return services.getOrPut(clazz) { instantiate(clazz) } as T } private fun <T : Any> instantiate(clazz: KClass<T>): T { val constructor = clazz.constructors .singleOrNull { it.hasAnnotation<Inject>() } ?: checkNotNull(clazz.primaryConstructor) val arguments = constructor.parameters.map { parameter -> get(parameter.type.classifier as KClass<*>) } return constructor.call(*arguments.toTypedArray()) } }
  25. Invoke constructors class Component { fun <T : Any> get(clazz:

    KClass<T>): T { @Suppress("UNCHECKED_CAST") return services.getOrPut(clazz) { instantiate(clazz) } as T } private fun <T : Any> instantiate(clazz: KClass<T>): T { val constructor = clazz.constructors .singleOrNull { it.hasAnnotation<Inject>() } ?: checkNotNull(clazz.primaryConstructor) val arguments = constructor.parameters.map { parameter -> get(parameter.type.classifier as KClass<*>) } return constructor.call(*arguments.toTypedArray()) } }
  26. Invoke constructors class Component { fun <T : Any> get(clazz:

    KClass<T>): T { @Suppress("UNCHECKED_CAST") return services.getOrPut(clazz) { instantiate(clazz) } as T } private fun <T : Any> instantiate(clazz: KClass<T>): T { val constructor = clazz.constructors .singleOrNull { it.hasAnnotation<Inject>() } ?: checkNotNull(clazz.primaryConstructor) val arguments = constructor.parameters.map { parameter -> get(parameter.type.classifier as KClass<*>) } return constructor.call(*arguments.toTypedArray()) } }
  27. Invoke constructors class Component { fun <T : Any> get(clazz:

    KClass<T>): T { @Suppress("UNCHECKED_CAST") return services.getOrPut(clazz) { instantiate(clazz) } as T } private fun <T : Any> instantiate(clazz: KClass<T>): T { val constructor = clazz.constructors .singleOrNull { it.hasAnnotation<Inject>() } ?: checkNotNull(clazz.primaryConstructor) val arguments = constructor.parameters.map { parameter -> get(parameter.type.classifier as KClass<*>) } return constructor.call(*arguments.toTypedArray()) } }
  28. Invoke constructors class Component { fun <T : Any> get(clazz:

    KClass<T>): T { @Suppress("UNCHECKED_CAST") return services.getOrPut(clazz) { instantiate(clazz) } as T } private fun <T : Any> instantiate(clazz: KClass<T>): T { val constructor = clazz.constructors .singleOrNull { it.hasAnnotation<Inject>() } ?: checkNotNull(clazz.primaryConstructor) val arguments = constructor.parameters.map { parameter -> get(parameter.type.classifier as KClass<*>) } return constructor.call(*arguments.toTypedArray()) } }
  29. Invoke constructors class RealTimeLocationProvider : LocationProvider class WeatherRepositoryImpl( val locationProvider:

    LocationProvider, ) : WeatherRepository class WeatherReport( val weatherRepository: WeatherRepository, val locationProvider: LocationProvider, )
  30. Invoke constructors @Inject class RealTimeLocationProvider : LocationProvider @Inject class WeatherRepositoryImpl(

    val locationProvider: LocationProvider, ) : WeatherRepository @Inject class WeatherReport( val weatherRepository: WeatherRepository, val locationProvider: LocationProvider, )
  31. Invoke constructors fun main() { val component = Component() component.get<WeatherReport>()

    } java.lang.IllegalStateException: Required value was null. at software.amazon.lastmile.kotlin.inject.anvil.sample.Component.instantiate(AndroidAppComponent.kt:75) at software.amazon.lastmile.kotlin.inject.anvil.sample.Component.get(AndroidAppComponent.kt:65) at software.amazon.lastmile.kotlin.inject.anvil.sample.Component.instantiate(AndroidAppComponent.kt:78) at software.amazon.lastmile.kotlin.inject.anvil.sample.Component.get(AndroidAppComponent.kt:65)
  32. Invoke constructors fun main() { val component = Component().apply {

    register(LocationProvider::class, get<RealTimeLocationProvider>()) register(WeatherRepository::class, get<WeatherRepositoryImpl>()) } component.get<WeatherReport>() }
  33. Invoke constructors fun main() { val component = Component().apply {

    bind<LocationProvider, RealTimeLocationProvider>() bind<WeatherRepository, WeatherRepositoryImpl>() } component.get<WeatherReport>() }
  34. Problems service locator • No compile time safety • No

    circular dependency detection • Race conditions • Boilerplate (a little less) • Service locator
  35. Problems service locator • No compile time safety • No

    circular dependency detection • Race conditions • Boilerplate (a little less) • Service locator • JVM / Android only • Runtime overhead
  36. Problems service locator object Component { val locationProvider: LocationProvider =

    RealTimeLocationProvider() val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) val weatherReport = WeatherReport(weatherRepository, locationProvider) } fun showWeatherReport() { Component.weatherReport }
  37. Combined solution • No compile time safety • No circular

    dependency detection • Race conditions • Boilerplate (a little less) • Service locator • JVM / Android only • Runtime overhead • Replacing dependencies in tests • Scopes with shorter lifecycle • Adding classes we don’t own • Component needs to be updated frequently (high maintenance) • Component needs to live in the application modules (duplication) • Invoke constructors automatically
  38. Combined solution • No compile time safety • No circular

    dependency detection • Race conditions • Boilerplate • Service locator • JVM / Android only • Runtime overhead • Replacing dependencies in tests • Scopes with shorter lifecycle • Adding classes we don’t own • Component needs to be updated frequently (high maintenance) • Component needs to live in the application modules (duplication) • Invoke constructors automatically
  39. Replacing dependencies in tests object Component { val locationProvider: LocationProvider

    = RealTimeLocationProvider() val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) val weatherReport = WeatherReport(weatherRepository, locationProvider) }
  40. Replacing dependencies in tests class Component( val locationProvider: LocationProvider =

    RealTimeLocationProvider(), ) { val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) val weatherReport = WeatherReport(weatherRepository, locationProvider) }
  41. Replacing dependencies in tests class Component( val locationProvider: LocationProvider =

    RealTimeLocationProvider(), val weatherReport: WeatherReport = WeatherReport(weatherRepository, locationProvider) ) { val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) }
  42. Replacing dependencies in tests interface BaseComponent { val locationProvider: LocationProvider

    val weatherReport: WeatherReport } class AppComponent : BaseComponent { override val locationProvider: LocationProvider = RealTimeLocationProvider() val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) override val weatherReport = WeatherReport(weatherRepository, locationProvider) } class TestComponent : BaseComponent { override val locationProvider: LocationProvider = FakeLocationProvider() val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) override val weatherReport = WeatherReport(weatherRepository, locationProvider) }
  43. Replacing dependencies in tests interface BaseComponent { val locationProvider: LocationProvider

    val weatherReport: WeatherReport } class AppComponent : BaseComponent { override val locationProvider: LocationProvider = RealTimeLocationProvider() val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) override val weatherReport = WeatherReport(weatherRepository, locationProvider) } class TestComponent : BaseComponent { override val locationProvider: LocationProvider = FakeLocationProvider() val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) override val weatherReport = WeatherReport(weatherRepository, locationProvider) }
  44. Combined solution • No compile time safety • No circular

    dependency detection • Race conditions • Boilerplate • Service locator • JVM / Android only • Runtime overhead • Replacing dependencies in tests • Scopes with shorter lifecycle • Adding classes we don’t own • Component needs to be updated frequently (high maintenance) • Component needs to live in the application modules (duplication) • Invoke constructors automatically
  45. Combined solution • No compile time safety • No circular

    dependency detection • Race conditions • Boilerplate • Service locator • JVM / Android only • Runtime overhead • Replacing dependencies in tests • Scopes with shorter lifecycle • Adding classes we don’t own • Component needs to be updated frequently (high maintenance) • Component needs to live in the application modules (duplication) • Invoke constructors automatically
  46. Scopes with shorter lifecycle class AppComponent { val locationProvider: LocationProvider

    = RealTimeLocationProvider() val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) val weatherReport = WeatherReport(weatherRepository, locationProvider) }
  47. Scopes with shorter lifecycle class AppComponent { val locationProvider: LocationProvider

    = RealTimeLocationProvider() val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) val weatherReport = WeatherReport(weatherRepository, locationProvider) } class LoggedInComponent( appComponent: AppComponent, ) { val savedLocations: SavedLocations = SavedLocationsImpl(appComponent.locationProvider) }
  48. Scopes with shorter lifecycle class AppComponent { val locationProvider: LocationProvider

    = RealTimeLocationProvider() val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) val weatherReport = WeatherReport(weatherRepository, locationProvider) } class LoggedInComponent( appComponent: AppComponent, ) { val savedLocations: SavedLocations = SavedLocationsImpl(appComponent.locationProvider) }
  49. Combined solution • No compile time safety • No circular

    dependency detection • Race conditions • Boilerplate • Service locator • JVM / Android only • Runtime overhead • Replacing dependencies in tests • Scopes with shorter lifecycle • Adding classes we don’t own • Component needs to be updated frequently (high maintenance) • Component needs to live in the application modules (duplication) • Invoke constructors automatically
  50. Combined solution • No compile time safety • No circular

    dependency detection • Race conditions • Boilerplate • Service locator • JVM / Android only • Runtime overhead • Replacing dependencies in tests • Scopes with shorter lifecycle • Adding classes we don’t own • Component needs to be updated frequently (high maintenance) • Component needs to live in the application modules (duplication) • Invoke constructors automatically
  51. Adding classes we don’t own class AppComponent { val locationProvider:

    LocationProvider = RealTimeLocationProvider() val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) val weatherReport = WeatherReport(weatherRepository, locationProvider) } class LoggedInComponent( appComponent: AppComponent, ) { val savedLocations: SavedLocations = SavedLocationsImpl(appComponent.locationProvider) }
  52. Adding classes we don’t own class AppComponent { val locationProvider:

    LocationProvider = RealTimeLocationProvider() val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) val weatherReport = WeatherReport(weatherRepository, locationProvider) } class LoggedInComponent( appComponent: AppComponent, authToken: String, ) { val savedLocations: SavedLocations = SavedLocationsImpl(appComponent.locationProvider) }
  53. Combined solution • No compile time safety • No circular

    dependency detection • Race conditions • Boilerplate • Service locator • JVM / Android only • Runtime overhead • Replacing dependencies in tests • Scopes with shorter lifecycle • Adding classes we don’t own • Component needs to be updated frequently (high maintenance) • Component needs to live in the application modules (duplication) • Invoke constructors automatically
  54. Combined solution • No compile time safety • No circular

    dependency detection • Race conditions • Boilerplate • Service locator • JVM / Android only • Runtime overhead • Replacing dependencies in tests • Scopes with shorter lifecycle • Adding classes we don’t own • Component needs to be updated frequently (high maintenance) • Component needs to live in the application modules (duplication) • Invoke constructors automatically
  55. Boilerplate interface BaseComponent { val locationProvider: LocationProvider val weatherReport: WeatherReport

    } class AppComponent : BaseComponent { override val locationProvider: LocationProvider = RealTimeLocationProvider() val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) override val weatherReport = WeatherReport(weatherRepository, locationProvider) } class TestComponent : BaseComponent { override val locationProvider: LocationProvider = FakeLocationProvider() val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) override val weatherReport = WeatherReport(weatherRepository, locationProvider) } class LoggedInComponent( appComponent: AppComponent, authToken: String, ) { val savedLocations: SavedLocations = SavedLocationsImpl(appComponent.locationProvider) }
  56. Boilerplate - invoke constructors interface BaseComponent { val locationProvider: LocationProvider

    val weatherReport: WeatherReport } class AppComponent : BaseComponent { override val locationProvider: LocationProvider = RealTimeLocationProvider() val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) override val weatherReport = WeatherReport(weatherRepository, locationProvider) } class TestComponent : BaseComponent { override val locationProvider: LocationProvider = FakeLocationProvider() val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) override val weatherReport = WeatherReport(weatherRepository, locationProvider) } class LoggedInComponent( appComponent: AppComponent, authToken: String, ) { val savedLocations: SavedLocations = SavedLocationsImpl(appComponent.locationProvider) }
  57. Boilerplate - invoke constructors interface BaseComponent { val locationProvider: LocationProvider

    val weatherReport: WeatherReport } class AppComponent : BaseComponent { override val locationProvider: LocationProvider = RealTimeLocationProvider() val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) override val weatherReport = WeatherReport(weatherRepository, locationProvider) } class TestComponent : BaseComponent { override val locationProvider: LocationProvider = FakeLocationProvider() val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) override val weatherReport = WeatherReport(weatherRepository, locationProvider) } class LoggedInComponent( appComponent: AppComponent, authToken: String, ) { val savedLocations: SavedLocations = SavedLocationsImpl(appComponent.locationProvider) }
  58. Boilerplate interface BaseComponent { val locationProvider: LocationProvider val weatherReport: WeatherReport

    } class AppComponent : BaseComponent { override val locationProvider: LocationProvider = RealTimeLocationProvider() val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) override val weatherReport = WeatherReport(weatherRepository, locationProvider) } class TestComponent : BaseComponent { override val locationProvider: LocationProvider = FakeLocationProvider() val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) override val weatherReport = WeatherReport(weatherRepository, locationProvider) } class LoggedInComponent( appComponent: AppComponent, authToken: String, ) { val savedLocations: SavedLocations = SavedLocationsImpl(appComponent.locationProvider) }
  59. Boilerplate interface BaseComponent { val locationProvider: LocationProvider val weatherReport: WeatherReport

    } class AppComponent : BaseComponent { fun bind(locationProvider: RealTimeLocationProvider): LocationProvider val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) override val weatherReport = WeatherReport(weatherRepository, locationProvider) } class TestComponent : BaseComponent { fun bind(locationProvider: FakeLocationProvider): LocationProvider val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) override val weatherReport = WeatherReport(weatherRepository, locationProvider) } class LoggedInComponent( appComponent: AppComponent, authToken: String, ) { val savedLocations: SavedLocations = SavedLocationsImpl(appComponent.locationProvider) }
  60. Boilerplate - invoke constructors interface BaseComponent { val locationProvider: LocationProvider

    val weatherReport: WeatherReport } class AppComponent : BaseComponent { fun bind(locationProvider: RealTimeLocationProvider): LocationProvider val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) override val weatherReport = WeatherReport(weatherRepository, locationProvider) } class TestComponent : BaseComponent { fun bind(locationProvider: FakeLocationProvider): LocationProvider val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) override val weatherReport = WeatherReport(weatherRepository, locationProvider) } class LoggedInComponent( appComponent: AppComponent, authToken: String, ) { val savedLocations: SavedLocations = SavedLocationsImpl(appComponent.locationProvider) }
  61. Boilerplate interface BaseComponent { val locationProvider: LocationProvider val weatherReport: WeatherReport

    } class AppComponent : BaseComponent { fun bind(locationProvider: RealTimeLocationProvider): LocationProvider val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) override val weatherReport = WeatherReport(weatherRepository, locationProvider) } class TestComponent : BaseComponent { fun bind(locationProvider: FakeLocationProvider): LocationProvider val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) override val weatherReport = WeatherReport(weatherRepository, locationProvider) } class LoggedInComponent( appComponent: AppComponent, authToken: String, ) { val savedLocations: SavedLocations = SavedLocationsImpl(appComponent.locationProvider) }
  62. Boilerplate interface BaseComponent { val locationProvider: LocationProvider val weatherReport: WeatherReport

    fun bind(weatherRepository: WeatherRepositoryImpl): WeatherRepository } class AppComponent : BaseComponent { fun bind(locationProvider: RealTimeLocationProvider): LocationProvider val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) override val weatherReport = WeatherReport(weatherRepository, locationProvider) } class TestComponent : BaseComponent { fun bind(locationProvider: FakeLocationProvider): LocationProvider val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) override val weatherReport = WeatherReport(weatherRepository, locationProvider) } class LoggedInComponent( appComponent: AppComponent, authToken: String, ) { val savedLocations: SavedLocations = SavedLocationsImpl(appComponent.locationProvider) }
  63. Boilerplate - invoke constructors interface BaseComponent { val locationProvider: LocationProvider

    val weatherReport: WeatherReport fun bind(weatherRepository: WeatherRepositoryImpl): WeatherRepository } class AppComponent : BaseComponent { fun bind(locationProvider: RealTimeLocationProvider): LocationProvider val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) override val weatherReport = WeatherReport(weatherRepository, locationProvider) } class TestComponent : BaseComponent { fun bind(locationProvider: FakeLocationProvider): LocationProvider val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) override val weatherReport = WeatherReport(weatherRepository, locationProvider) } class LoggedInComponent( appComponent: AppComponent, authToken: String, ) { val savedLocations: SavedLocations = SavedLocationsImpl(appComponent.locationProvider) }
  64. Boilerplate - invoke constructors interface BaseComponent { val locationProvider: LocationProvider

    val weatherReport: WeatherReport fun bind(weatherRepository: WeatherRepositoryImpl): WeatherRepository } class AppComponent : BaseComponent { fun bind(locationProvider: RealTimeLocationProvider): LocationProvider val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) override val weatherReport = WeatherReport(weatherRepository, locationProvider) } class TestComponent : BaseComponent { fun bind(locationProvider: FakeLocationProvider): LocationProvider val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) override val weatherReport = WeatherReport(weatherRepository, locationProvider) } class LoggedInComponent( appComponent: AppComponent, authToken: String, ) { fun bind(savedLocations: SavedLocationsImpl): SavedLocations }
  65. Combined solution • No compile time safety • No circular

    dependency detection • Race conditions • Boilerplate • Service locator • JVM / Android only • Runtime overhead • Replacing dependencies in tests • Scopes with shorter lifecycle • Adding classes we don’t own • Component needs to be updated frequently (high maintenance) • Component needs to live in the application modules (duplication) • Invoke constructors automatically
  66. Combined solution • No compile time safety • No circular

    dependency detection • Race conditions • Boilerplate • Service locator • JVM / Android only • Runtime overhead • Replacing dependencies in tests • Scopes with shorter lifecycle • Adding classes we don’t own • Component needs to be updated frequently (high maintenance) • Component needs to live in the application modules (duplication) • Invoke constructors automatically
  67. Boilerplate - bindings interface BaseComponent { val locationProvider: LocationProvider val

    weatherReport: WeatherReport fun bind(weatherRepository: WeatherRepositoryImpl): WeatherRepository } class AppComponent : BaseComponent { fun bind(locationProvider: RealTimeLocationProvider): LocationProvider val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) override val weatherReport = WeatherReport(weatherRepository, locationProvider) } class TestComponent : BaseComponent { fun bind(locationProvider: FakeLocationProvider): LocationProvider val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) override val weatherReport = WeatherReport(weatherRepository, locationProvider) } class LoggedInComponent( appComponent: AppComponent, authToken: String, ) { fun bind(savedLocations: SavedLocationsImpl): SavedLocations }
  68. Boilerplate - bindings @Inject class RealTimeLocationProvider : LocationProvider @Inject class

    FakeLocationProvider : LocationProvider @Inject class WeatherRepositoryImpl( val locationProvider: LocationProvider, ) : WeatherRepository
  69. Boilerplate - bindings @Inject @ContributesBinding class RealTimeLocationProvider : LocationProvider @Inject

    @ContributesBinding class FakeLocationProvider : LocationProvider @Inject @ContributesBinding class WeatherRepositoryImpl( val locationProvider: LocationProvider, ) : WeatherRepository
  70. Boilerplate - bindings @Inject @ContributesBinding class RealTimeLocationProvider : LocationProvider @Inject

    @ContributesBinding(replaces = RealTimeLocationProvider::class) class FakeLocationProvider : LocationProvider @Inject @ContributesBinding class WeatherRepositoryImpl( val locationProvider: LocationProvider, ) : WeatherRepository
  71. Boilerplate - bindings interface BaseComponent { val locationProvider: LocationProvider val

    weatherReport: WeatherReport fun bind(weatherRepository: WeatherRepositoryImpl): WeatherRepository } class AppComponent : BaseComponent { fun bind(locationProvider: RealTimeLocationProvider): LocationProvider val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) override val weatherReport = WeatherReport(weatherRepository, locationProvider) } class TestComponent : BaseComponent { fun bind(locationProvider: FakeLocationProvider): LocationProvider val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) override val weatherReport = WeatherReport(weatherRepository, locationProvider) } class LoggedInComponent( appComponent: AppComponent, authToken: String, ) { fun bind(savedLocations: SavedLocationsImpl): SavedLocations }
  72. Boilerplate - bindings interface BaseComponent { val locationProvider: LocationProvider val

    weatherReport: WeatherReport fun bind(weatherRepository: WeatherRepositoryImpl): WeatherRepository } class AppComponent : BaseComponent { fun bind(locationProvider: RealTimeLocationProvider): LocationProvider val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) override val weatherReport = WeatherReport(weatherRepository, locationProvider) } class TestComponent : BaseComponent { fun bind(locationProvider: FakeLocationProvider): LocationProvider val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) override val weatherReport = WeatherReport(weatherRepository, locationProvider) } class LoggedInComponent( appComponent: AppComponent, authToken: String, ) { fun bind(savedLocations: SavedLocationsImpl): SavedLocations }
  73. Boilerplate - component interfaces interface BaseComponent { val locationProvider: LocationProvider

    val weatherReport: WeatherReport fun bind(weatherRepository: WeatherRepositoryImpl): WeatherRepository } class AppComponent : BaseComponent { fun bind(locationProvider: RealTimeLocationProvider): LocationProvider val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) override val weatherReport = WeatherReport(weatherRepository, locationProvider) } class TestComponent : BaseComponent { fun bind(locationProvider: FakeLocationProvider): LocationProvider val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) override val weatherReport = WeatherReport(weatherRepository, locationProvider) }
  74. Boilerplate - component interfaces @ContributesTo(AppScope::class) interface BaseComponent { val locationProvider:

    LocationProvider val weatherReport: WeatherReport fun bind(weatherRepository: WeatherRepositoryImpl): WeatherRepository } @MergeComponent(AppScope::class) class AppComponent : BaseComponent { fun bind(locationProvider: RealTimeLocationProvider): LocationProvider val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) override val weatherReport = WeatherReport(weatherRepository, locationProvider) } @MergeComponent(AppScope::class) class TestComponent : BaseComponent { fun bind(locationProvider: FakeLocationProvider): LocationProvider val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) override val weatherReport = WeatherReport(weatherRepository, locationProvider) }
  75. Boilerplate - component interfaces @ContributesTo(AppScope::class) interface BaseComponent { val locationProvider:

    LocationProvider val weatherReport: WeatherReport fun bind(weatherRepository: WeatherRepositoryImpl): WeatherRepository } @MergeComponent(AppScope::class) class AppComponent { fun bind(locationProvider: RealTimeLocationProvider): LocationProvider val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) override val weatherReport = WeatherReport(weatherRepository, locationProvider) } @MergeComponent(AppScope::class) class TestComponent { fun bind(locationProvider: FakeLocationProvider): LocationProvider val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) override val weatherReport = WeatherReport(weatherRepository, locationProvider) }
  76. Boilerplate - component interfaces @ContributesTo(AppScope::class) interface BaseComponent { val locationProvider:

    LocationProvider val weatherReport: WeatherReport } @MergeComponent(AppScope::class) class AppComponent @MergeComponent(AppScope::class) class TestComponent class LoggedInComponent( appComponent: AppComponent, authToken: String, )
  77. Combined solution • No compile time safety • No circular

    dependency detection • Race conditions • Boilerplate • Service locator • JVM / Android only • Runtime overhead • Replacing dependencies in tests • Scopes with shorter lifecycle • Adding classes we don’t own • Component needs to be updated frequently (high maintenance) • Component needs to live in the application modules (duplication) • Invoke constructors automatically
  78. Combined solution • No compile time safety • No circular

    dependency detection • Race conditions • Boilerplate • Service locator • JVM / Android only • Runtime overhead • Replacing dependencies in tests • Scopes with shorter lifecycle • Adding classes we don’t own • Component needs to be updated frequently (high maintenance) • Component needs to live in the application modules (duplication) • Invoke constructors automatically
  79. Boilerplate - meta programming - KSP @ContributesTo(AppScope::class) interface BaseComponent {

    val locationProvider: LocationProvider val weatherReport: WeatherReport } @MergeComponent(AppScope::class) class AppComponent @MergeComponent(AppScope::class) class TestComponent class LoggedInComponent( appComponent: AppComponent, authToken: String, )
  80. @ContributesTo(AppScope::class) interface BaseComponent { val locationProvider: LocationProvider val weatherReport: WeatherReport

    } @MergeComponent(AppScope::class) interface AppComponent @MergeComponent(AppScope::class) interface TestComponent abstract class LoggedInComponent( appComponent: AppComponent, authToken: String, ) Boilerplate - meta programming - KSP
  81. @ContributesTo(AppScope::class) interface BaseComponent { val locationProvider: LocationProvider val weatherReport: WeatherReport

    } @MergeComponent(AppScope::class) interface AppComponent Boilerplate - meta programming - KSP
  82. Boilerplate - meta programming - KSP @ContributesTo(AppScope::class) interface BaseComponent {

    val locationProvider: LocationProvider val weatherReport: WeatherReport } @MergeComponent(AppScope::class) interface AppComponent
  83. Boilerplate - meta programming - KSP @ContributesTo(AppScope::class) interface BaseComponent {

    val locationProvider: LocationProvider val weatherReport: WeatherReport } @MergeComponent(AppScope::class) interface AppComponent class FinalAppComponent : AppComponent { }
  84. Boilerplate - meta programming - KSP @ContributesTo(AppScope::class) interface BaseComponent {

    val locationProvider: LocationProvider val weatherReport: WeatherReport } @MergeComponent(AppScope::class) interface AppComponent class FinalAppComponent : AppComponent, BaseComponent { }
  85. Boilerplate - meta programming - KSP @ContributesTo(AppScope::class) interface BaseComponent {

    val locationProvider: LocationProvider val weatherReport: WeatherReport } @MergeComponent(AppScope::class) interface AppComponent class FinalAppComponent : AppComponent, BaseComponent { override val locationProvider: LocationProvider = ??? override val weatherReport: WeatherReport = ??? }
  86. Boilerplate - meta programming - KSP @Inject @ContributesBinding class RealTimeLocationProvider

    : LocationProvider @MergeComponent(AppScope::class) interface AppComponent class FinalAppComponent : AppComponent, BaseComponent { override val locationProvider: LocationProvider = RealTimeLocationProvider() override val weatherReport: WeatherReport = ??? }
  87. Boilerplate - meta programming - KSP @ContributesTo(AppScope::class) interface BaseComponent {

    val locationProvider: LocationProvider val weatherReport: WeatherReport } @MergeComponent(AppScope::class) interface AppComponent class FinalAppComponent : AppComponent, BaseComponent { override val locationProvider: LocationProvider = RealTimeLocationProvider() override val weatherReport: WeatherReport = WeatherReport( weatherRepository = ???, locationProvider = ???, ) }
  88. Boilerplate - meta programming - KSP @ContributesTo(AppScope::class) interface BaseComponent {

    val locationProvider: LocationProvider val weatherReport: WeatherReport } @MergeComponent(AppScope::class) interface AppComponent class FinalAppComponent : AppComponent, BaseComponent { override val locationProvider: LocationProvider = RealTimeLocationProvider() override val weatherReport: WeatherReport = WeatherReport( weatherRepository = ???, locationProvider = locationProvider, ) }
  89. Boilerplate - meta programming - KSP @ContributesTo(AppScope::class) interface BaseComponent {

    val locationProvider: LocationProvider val weatherReport: WeatherReport } @MergeComponent(AppScope::class) interface AppComponent class FinalAppComponent : AppComponent, BaseComponent { override val locationProvider: LocationProvider = RealTimeLocationProvider() private val weatherRepository: WeatherRepository = ??? override val weatherReport: WeatherReport = WeatherReport( weatherRepository = weatherRepository, locationProvider = locationProvider, ) }
  90. Boilerplate - meta programming - KSP @Inject @ContributesBinding class WeatherRepositoryImpl(

    val locationProvider: LocationProvider, ) : WeatherRepository @MergeComponent(AppScope::class) interface AppComponent class FinalAppComponent : AppComponent, BaseComponent { override val locationProvider: LocationProvider = RealTimeLocationProvider() private val weatherRepository: WeatherRepository = WeatherRepositoryImpl( locationProvider = ???, ) override val weatherReport: WeatherReport = WeatherReport( weatherRepository = weatherRepository, locationProvider = locationProvider, ) }
  91. Boilerplate - meta programming - KSP @ContributesTo(AppScope::class) interface BaseComponent {

    val locationProvider: LocationProvider val weatherReport: WeatherReport } @MergeComponent(AppScope::class) interface AppComponent class FinalAppComponent : AppComponent, BaseComponent { override val locationProvider: LocationProvider = RealTimeLocationProvider() private val weatherRepository: WeatherRepository = WeatherRepositoryImpl( locationProvider = locationProvider, ) override val weatherReport: WeatherReport = WeatherReport( weatherRepository = weatherRepository, locationProvider = locationProvider, ) }
  92. kotlin-inject kotlin-inject is a compile-time dependency injection (DI) framework designed

    specifically for Kotlin. https://github.com/evant/kotlin-inject
  93. kotlin-inject-anvil kotlin-inject-anvil is an extension library kotlin-inject, designed to enhance

    its functionality by incorporating features inspired by Square’s Anvil library. https://github.com/amzn/kotlin-inject-anvil
  94. Prototype @Inject @ContributesBinding class RealTimeLocationProvider : LocationProvider @Inject @ContributesBinding(replaces =

    RealTimeLocationProvider::class) class FakeLocationProvider : LocationProvider @Inject @ContributesBinding class WeatherRepositoryImpl( val locationProvider: LocationProvider, ) : WeatherRepository
  95. kotlin-inject-anvil import me.tatarka.inject.annotations.Inject import software.amazon.lastmile.kotlin.inject.anvil.AppScope import software.amazon.lastmile.kotlin.inject.anvil.ContributesBinding import software.amazon.lastmile.kotlin.inject.anvil.SingleIn @Inject

    @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) class RealTimeLocationProvider : LocationProvider @Inject @SingleIn(AppScope::class) @ContributesBinding(AppScope::class, replaces = [RealTimeLocationProvider::class]) class FakeLocationProvider : LocationProvider @Inject @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) class WeatherRepositoryImpl( val locationProvider: LocationProvider, ) : WeatherRepository
  96. kotlin-inject-anvil import me.tatarka.inject.annotations.Inject import software.amazon.lastmile.kotlin.inject.anvil.AppScope import software.amazon.lastmile.kotlin.inject.anvil.ContributesBinding import software.amazon.lastmile.kotlin.inject.anvil.SingleIn @Inject

    @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) class RealTimeLocationProvider : LocationProvider @Inject @SingleIn(AppScope::class) @ContributesBinding(AppScope::class, replaces = [RealTimeLocationProvider::class]) class FakeLocationProvider : LocationProvider @Inject @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) class WeatherRepositoryImpl( val locationProvider: LocationProvider, ) : WeatherRepository
  97. kotlin-inject-anvil import me.tatarka.inject.annotations.Inject import software.amazon.lastmile.kotlin.inject.anvil.AppScope import software.amazon.lastmile.kotlin.inject.anvil.ContributesBinding import software.amazon.lastmile.kotlin.inject.anvil.SingleIn @Inject

    @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) class RealTimeLocationProvider : LocationProvider @Inject @SingleIn(AppScope::class) @ContributesBinding(AppScope::class, replaces = [RealTimeLocationProvider::class]) class FakeLocationProvider : LocationProvider @Inject @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) class WeatherRepositoryImpl( val locationProvider: LocationProvider, ) : WeatherRepository
  98. kotlin-inject-anvil import me.tatarka.inject.annotations.Inject import software.amazon.lastmile.kotlin.inject.anvil.AppScope import software.amazon.lastmile.kotlin.inject.anvil.ContributesBinding import software.amazon.lastmile.kotlin.inject.anvil.SingleIn @Inject

    @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) class RealTimeLocationProvider : LocationProvider @Inject @SingleIn(AppScope::class) @ContributesBinding(AppScope::class, replaces = [RealTimeLocationProvider::class]) class FakeLocationProvider : LocationProvider @Inject @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) class WeatherRepositoryImpl( val locationProvider: LocationProvider, ) : WeatherRepository
  99. Prototype @ContributesTo(AppScope::class) interface BaseComponent { val locationProvider: LocationProvider val weatherReport:

    WeatherReport } @MergeComponent(AppScope::class) interface AppComponent @MergeComponent(AppScope::class) interface TestComponent abstract class LoggedInComponent( appComponent: AppComponent, authToken: String, )
  100. kotlin-inject-anvil import software.amazon.lastmile.kotlin.inject.anvil.* @ContributesTo(AppScope::class) interface BaseComponent { val locationProvider: LocationProvider

    val weatherReport: WeatherReport } @SingleIn(AppScope::class) @MergeComponent(AppScope::class) interface AppComponent @SingleIn(AppScope::class) @MergeComponent(AppScope::class) interface TestComponent @SingleIn(LoggedInScope::class) @MergeComponent(LoggedInScope::class) abstract class LoggedInComponent( @Component val appComponent: AppComponent, authToken: String, )
  101. kotlin-inject-anvil import software.amazon.lastmile.kotlin.inject.anvil.* @ContributesTo(AppScope::class) interface BaseComponent { val locationProvider: LocationProvider

    val weatherReport: WeatherReport } @SingleIn(AppScope::class) @MergeComponent(AppScope::class) interface AppComponent @SingleIn(AppScope::class) @MergeComponent(AppScope::class) interface TestComponent @SingleIn(LoggedInScope::class) @MergeComponent(LoggedInScope::class) abstract class LoggedInComponent( @Component val appComponent: AppComponent, authToken: String, )
  102. kotlin-inject-anvil import software.amazon.lastmile.kotlin.inject.anvil.* @ContributesTo(AppScope::class) interface BaseComponent { val locationProvider: LocationProvider

    val weatherReport: WeatherReport } @SingleIn(AppScope::class) @MergeComponent(AppScope::class) interface AppComponent @SingleIn(AppScope::class) @MergeComponent(AppScope::class) interface TestComponent @SingleIn(LoggedInScope::class) @MergeComponent(LoggedInScope::class) abstract class LoggedInComponent( @Component val appComponent: AppComponent, authToken: String, )
  103. kotlin-inject-anvil import software.amazon.lastmile.kotlin.inject.anvil.* @ContributesTo(AppScope::class) interface BaseComponent { val locationProvider: LocationProvider

    val weatherReport: WeatherReport } @SingleIn(AppScope::class) @MergeComponent(AppScope::class) interface AppComponent @SingleIn(AppScope::class) @MergeComponent(AppScope::class) interface TestComponent @SingleIn(LoggedInScope::class) @MergeComponent(LoggedInScope::class) abstract class LoggedInComponent( @Component val appComponent: AppComponent, authToken: String, )
  104. kotlin-inject-anvil // src/commonMain @SingleIn(AppScope::class) @MergeComponent(AppScope::class) interface AppComponent @CreateComponent expect fun

    KClass<AppComponent>.createComponent(): AppComponent // generated/ksp/android // generated/ksp/iosArm64 // generated/ksp/iosSimulatorArm64 // … class KotlinInjectAppComponent : AppComponent https://kotlinlang.org/docs/whatsnew20.html#separation-of-common-and-platform-sources-during-compilation
  105. kotlin-inject-anvil - features • Provider methods • Qualifiers • Multi-bindings

    • Assisted injection • Lazy injection • Default argument support • Overridable provider methods • Child components • Component arguments
  106. kotlin-inject-anvil - benefits • KMP support • Compile time safety

    • Little to no boilerplate • Aligns build graph with Gradle dependency graph • Easy to replace bindings in tests • Extensible with custom code generators
  107. kotlin-inject-anvil - critique • No wide adoption • Slower build

    times compared to vanilla Kotlin due to KSP • Learning curve • Unclear error messages
  108. kotlin-inject-anvil - our setup :van-app AndroidAppComponent All library modules :demo-app

    androidMain/kotlin/AndroidAppComponent iosMain/kotlin/IosAppComponent desktopMain/kotlin/DesktopAppComponent :phone-ios-umbrella IosAppComponent /iosPhoneProject /iosDemoProject :phone-android-app AndroidAppComponent
  109. :location kotlin-inject-anvil - our setup :public commonMain/kotlin/LocationProvider :impl androidMain/kotlin/AndroidLocationProvider iosMain

    /kotlin/IosLocationProvider :weather :public commonMain/kotlin/WeatherRepository :impl commonMain/kotlin/WeatherRepositoryImpl
  110. kotlin-inject-anvil - our setup :location :public commonMain/kotlin/LocationProvider :impl androidMain/kotlin/AndroidLocationProvider iosMain

    /kotlin/IosLocationProvider :weather :public commonMain/kotlin/WeatherRepository :impl commonMain/kotlin/WeatherRepositoryImpl
  111. kotlin-inject-anvil - our setup :location :public commonMain/kotlin/LocationProvider :impl-phone androidMain/kotlin/AndroidLocationProvider iosMain

    /kotlin/IosLocationProvider :impl-van androidMain/kotlin/VanLocationProvider :weather :public commonMain/kotlin/WeatherRepository :impl commonMain/kotlin/WeatherRepositoryImpl
  112. kotlin-inject-anvil - our setup :location :public commonMain/kotlin/LocationProvider :impl-phone androidMain/kotlin/AndroidLocationProvider iosMain

    /kotlin/IosLocationProvider :impl-van androidMain/kotlin/VanLocationProvider :weather :public commonMain/kotlin/WeatherRepository :impl commonMain/kotlin/WeatherRepositoryImpl :van-app AndroidAppComponent :demo-app androidMain/kotlin/AndroidAppComponent iosMain/kotlin/IosAppComponent desktopMain/kotlin/DesktopAppComponent :phone-ios-umbrella IosAppComponent :phone-android-app AndroidAppComponent
  113. kotlin-inject-anvil - our setup :location :public commonMain/kotlin/LocationProvider :impl-phone androidMain/kotlin/AndroidLocationProvider iosMain

    /kotlin/IosLocationProvider :impl-van androidMain/kotlin/VanLocationProvider :weather :public commonMain/kotlin/WeatherRepository :impl commonMain/kotlin/WeatherRepositoryImpl :van-app AndroidAppComponent :demo-app androidMain/kotlin/AndroidAppComponent iosMain/kotlin/IosAppComponent desktopMain/kotlin/DesktopAppComponent :phone-ios-umbrella IosAppComponent :phone-android-app AndroidAppComponent
  114. kotlin-inject-anvil - our setup :location :public commonMain/kotlin/LocationProvider :impl-phone androidMain/kotlin/AndroidLocationProvider iosMain

    /kotlin/IosLocationProvider :impl-van androidMain/kotlin/VanLocationProvider :weather :public commonMain/kotlin/WeatherRepository :impl commonMain/kotlin/WeatherRepositoryImpl :van-app AndroidAppComponent :demo-app androidMain/kotlin/AndroidAppComponent iosMain/kotlin/IosAppComponent desktopMain/kotlin/DesktopAppComponent :phone-ios-umbrella IosAppComponent :phone-android-app AndroidAppComponent
  115. kotlin-inject-anvil - our setup :location :public commonMain/kotlin/LocationProvider :impl-phone androidMain/kotlin/AndroidLocationProvider iosMain

    /kotlin/IosLocationProvider :impl-van androidMain/kotlin/VanLocationProvider :weather :public commonMain/kotlin/WeatherRepository :impl commonMain/kotlin/WeatherRepositoryImpl :van-app AndroidAppComponent :demo-app androidMain/kotlin/AndroidAppComponent iosMain/kotlin/IosAppComponent desktopMain/kotlin/DesktopAppComponent :phone-ios-umbrella IosAppComponent :phone-android-app AndroidAppComponent
  116. kotlin-inject-anvil - our setup :location :public commonMain/kotlin/LocationProvider :impl-phone androidMain/kotlin/AndroidLocationProvider iosMain

    /kotlin/IosLocationProvider :impl-van androidMain/kotlin/VanLocationProvider :weather :public commonMain/kotlin/WeatherRepository :impl commonMain/kotlin/WeatherRepositoryImpl :van-app AndroidAppComponent :demo-app androidMain/kotlin/AndroidAppComponent iosMain/kotlin/IosAppComponent desktopMain/kotlin/DesktopAppComponent :phone-ios-umbrella IosAppComponent :phone-android-app AndroidAppComponent
  117. kotlin-inject-anvil - Android instrumented tests :location :public commonMain/kotlin/LocationProvider :impl commonMain/kotlin/RealTimeLocationProvider

    :impl-robots commonMain/kotlin/FakeLocationProvider :demo-app androidMain/kotlin/AndroidAppComponent androidInstrumentedTest/kotlin/AndroidTestAppComponent
  118. kotlin-inject-anvil - Android instrumented tests :location :public commonMain/kotlin/LocationProvider :impl commonMain/kotlin/RealTimeLocationProvider

    :impl-robots commonMain/kotlin/FakeLocationProvider :demo-app androidMain/kotlin/AndroidAppComponent androidInstrumentedTest/kotlin/AndroidTestAppComponent
  119. kotlin-inject-anvil - Android instrumented tests :location :public commonMain/kotlin/LocationProvider :impl commonMain/kotlin/RealTimeLocationProvider

    :impl-robots commonMain/kotlin/FakeLocationProvider :demo-app androidMain/kotlin/AndroidAppComponent androidInstrumentedTest/kotlin/AndroidTestAppComponent
  120. kotlin-inject-anvil - constructor injection // weather/impl/src/commonMain/kotlin @Inject @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) class

    WeatherRepositoryImpl( val locationProvider: LocationProvider, ) : WeatherRepository // location/public/src/commonMain/kotlin interface LocationProvider
  121. kotlin-inject-anvil - constructor injection // weather/impl/src/commonMain/kotlin @Inject @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) class

    WeatherRepositoryImpl( val locationProvider: LocationProvider, ) : WeatherRepository // location/public/src/commonMain/kotlin interface LocationProvider // location/impl/src/commonMain/kotlin @Inject @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) class RealTimeLocationProvider : LocationProvider
  122. kotlin-inject-anvil - constructor injection interface NavigationManager { val screen: StateFlow<Screen>

    fun goTo(screen: Screen) } @Inject @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) class NavigationManagerImpl : NavigationManager { private val _screen = MutableStateFlow(Screen.COUNTER) override val screen: StateFlow<Screen> = _screen override fun goTo(screen: Screen) { _screen.update { screen } } }
  123. kotlin-inject-anvil - constructor injection interface CounterPresenter : MoleculePresenter<Unit, Model> @Inject

    @ContributesBinding(AppScope::class) class CounterPresenterImpl( private val counter: Counter, ) : CounterPresenter { @Composable override fun present(input: Unit): Model { ... } }
  124. kotlin-inject-anvil - constructor injection @Inject class NavigationPresenter( private val navigationManager:

    NavigationManager, private val lazyCounterPresenter: () -> CounterPresenter, private val lazyItemPresenter: () -> ItemListDetailPresenter, ) : MoleculePresenter<Unit, Template> { @Composable override fun present(input: Unit): Template { ... } }
  125. kotlin-inject-anvil - constructor injection @ContributesRobot(AppScope::class) class ComposeCounterRobot : ComposeRobot() {

    fun seeCounterView() { ... } fun counterIsIncrementing() { ... } fun clickCounterText() { ... } }
  126. kotlin-inject-anvil - retrieve objects class MainActivity : ComponentActivity() { private

    val rootScopeProvider get() = application as RootScopeProvider private val rendererFactoryRepository get() = rootScopeProvider.rootScope .diComponent<Component>() .rendererFactoryRepository private val navigationManager get() = rootScopeProvider.rootScope .diComponent<Component>() .navigationManager @ContributesTo(AppScope::class) interface Component { val rendererFactoryRepository: RendererFactoryRepository val navigationManager: NavigationManager } }
  127. kotlin-inject-anvil - retrieve objects class MainActivity : ComponentActivity() { private

    val rootScopeProvider get() = application as RootScopeProvider private val rendererFactoryRepository get() = rootScopeProvider.rootScope .diComponent<Component>() .rendererFactoryRepository private val navigationManager get() = rootScopeProvider.rootScope .diComponent<Component>() .navigationManager @ContributesTo(AppScope::class) interface Component { val rendererFactoryRepository: RendererFactoryRepository val navigationManager: NavigationManager } }
  128. kotlin-inject-anvil - retrieve objects class MainActivity : ComponentActivity() { private

    val rootScopeProvider get() = application as RootScopeProvider private val rendererFactoryRepository get() = rootScopeProvider.rootScope .diComponent<Component>() .rendererFactoryRepository private val navigationManager get() = rootScopeProvider.rootScope .diComponent<Component>() .navigationManager @ContributesTo(AppScope::class) interface Component { val rendererFactoryRepository: RendererFactoryRepository val navigationManager: NavigationManager } }
  129. kotlin-inject-anvil - retrieve objects class MainActivity : ComponentActivity() { private

    val rootScopeProvider get() = application as RootScopeProvider private val rendererFactoryRepository get() = rootScopeProvider.rootScope .diComponent<Component>() .rendererFactoryRepository private val navigationManager get() = rootScopeProvider.rootScope .diComponent<Component>() .navigationManager @ContributesTo(AppScope::class) interface Component { val rendererFactoryRepository: RendererFactoryRepository val navigationManager: NavigationManager } }
  130. kotlin-inject-anvil - provide Android dependencies // androidApp/src/androidMain/kotlin @SingleIn(AppScope::class) @MergeComponent(AppScope::class) abstract

    class AndroidAppComponent( @get:Provides val application: Application, ) // location/impl/src/androidMain/kotlin @ContributesTo(AppScope::class) interface LocationManagerComponent { @Provides fun provideLocationManager( application: Application ): LocationManager = checkNotNull(application.getSystemService<LocationManager>()) }
  131. kotlin-inject-anvil - provide iOS dependencies // iosApp/src/iosMain/kotlin @SingleIn(AppScope::class) @MergeComponent(AppScope::class) abstract

    class IosAppComponent( @get:Provides val uiApplication: UIApplication, ) @MergeComponent.CreateComponent expect fun KClass<IosAppComponent>.createComponent(uiApplication: UIApplication): IosAppComponent
  132. kotlin-inject-anvil - provide iOS dependencies // iosApp/src/iosMain/kotlin @SingleIn(AppScope::class) @MergeComponent(AppScope::class) abstract

    class IosAppComponent( @get:Provides val iosNativeImplementations: IosNativeImplementations, ) @MergeComponent.CreateComponent expect fun KClass<IosAppComponent>.createComponent(uiApplication: UIApplication): IosAppComponent interface IosNativeImplementations { val uiApplication: UIApplication @ContributesTo(AppScope::class) interface Component { @Provides fun provideUiApplication( iosNativeImplementations: IosNativeImplementations, ): UIApplication = iosNativeImplementations.uiApplication } }
  133. kotlin-inject-anvil - provide iOS dependencies // iosApp/src/iosMain/kotlin @SingleIn(AppScope::class) @MergeComponent(AppScope::class) abstract

    class IosAppComponent( @get:Provides val iosNativeImplementations: IosNativeImplementations, ) @MergeComponent.CreateComponent expect fun KClass<IosAppComponent>.createComponent(uiApplication: UIApplication): IosAppComponent interface IosNativeImplementations { val uiApplication: UIApplication @ContributesTo(AppScope::class) interface Component { @Provides fun provideUiApplication( iosNativeImplementations: IosNativeImplementations, ): UIApplication = iosNativeImplementations.uiApplication } }
  134. kotlin-inject-anvil - provide iOS dependencies // iosApp/src/iosMain/kotlin @SingleIn(AppScope::class) @MergeComponent(AppScope::class) abstract

    class IosAppComponent( @get:Provides val iosNativeImplementations: IosNativeImplementations, ) @MergeComponent.CreateComponent expect fun KClass<IosAppComponent>.createComponent(uiApplication: UIApplication): IosAppComponent interface IosNativeImplementations { val uiApplication: UIApplication @ContributesTo(AppScope::class) interface Component { @Provides fun provideUiApplication( iosNativeImplementations: IosNativeImplementations, ): UIApplication = iosNativeImplementations.uiApplication } }
  135. kotlin-inject-anvil - extensions @Inject class ComposeCounterRenderer : ComposeRenderer<Model>() { @ContributesTo(RendererScope::class)

    interface Component { @Provides @IntoMap fun provideComposeCounterRenderer( renderer: () -> ComposeCounterRenderer, ): Pair<KClass<out BaseModel>, () -> Renderer<*>> = Model::class to renderer } }
  136. kotlin-inject-anvil - extensions @Inject class ComposeCounterRobot : ComposeRobot() { @ContributesTo(AppScope::class)

    interface Component { @Provides @IntoMap fun provideComposeCounterRobot( robot: () -> ComposeCounterRobot, ): Pair<KClass<out Robot>, () -> Robot> = ComposeCounterRobot::class to robot } }
  137. kotlin-inject-anvil - extensions @Inject @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) class CounterImpl : Counter,

    Scoped { private val _count = MutableStateFlow(0) override val count: StateFlow<Int> get() = _count override fun onEnterScope(scope: Scope) { scope.launch { while (isActive) { delay(500L) _count.update { it + 1 } } } } }
  138. kotlin-inject-anvil - extensions @Inject @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) class CounterImpl : Counter,

    Scoped { private val _count = MutableStateFlow(0) override val count: StateFlow<Int> get() = _count override fun onEnterScope(scope: Scope) { scope.launch { while (isActive) { delay(500L) _count.update { it + 1 } } } } }
  139. kotlin-inject-anvil - expect-actual obsolete // Single :location module // location/src/commonMain/kotlin

    interface LocationProvider // location/src/commonMain/kotlin @Inject @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) class NetworkLocationProvider : LocationProvider
  140. kotlin-inject-anvil - expect-actual obsolete // Single :location module // location/src/commonMain/kotlin

    interface LocationProvider // location/src/commonMain/kotlin @Inject @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) class NetworkLocationProvider : LocationProvider // location/src/androidMain/kotlin @Inject @SingleIn(AppScope::class) @ContributesBinding(AppScope::class, replaces = [NetworkLocationProvider::class]) class AndroidLocationProvider(application: Application) : LocationProvider
  141. kotlin-inject-anvil - expect-actual obsolete // Single :location module // location/src/commonMain/kotlin

    interface LocationProvider // location/src/commonMain/kotlin @Inject @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) class NetworkLocationProvider : LocationProvider // location/src/androidMain/kotlin @Inject @SingleIn(AppScope::class) @ContributesBinding(AppScope::class, replaces = [NetworkLocationProvider::class]) class AndroidLocationProvider(application: Application) : LocationProvider // location/src/iosMain/kotlin @Inject @SingleIn(AppScope::class) @ContributesBinding(AppScope::class, replaces = [NetworkLocationProvider::class]) class IosLocationProvider(application: UIApplication) : LocationProvider
  142. kotlin-inject-anvil - common errors - duplicate binding > Task :sample:app:kspDebugKotlinAndroid

    FAILED e: [ksp] /app/build/generated/ksp/android/androidDebug/kotlin/amazon/lastmile/inject/SoftwareAmazonLastmileKotlin InjectAnvilSampleAndroidLocationProvider.kt:11: Cannot provide: software.amazon.lastmile.kotlin.inject.anvil.sample.LocationProvider e: [ksp] /app/build/generated/ksp/android/androidDebug/kotlin/amazon/lastmile/inject/SoftwareAmazonLastmileKotlin InjectAnvilSampleNetworkLocationProvider.kt:11: as it is already provided e: Error occurred in KSP, check log for detail
  143. kotlin-inject-anvil - common errors - duplicate binding > Task :sample:app:kspDebugKotlinAndroid

    FAILED e: [ksp] /app/build/generated/ksp/android/androidDebug/kotlin/amazon/lastmile/inject/SoftwareAmazonLastmileKotlin InjectAnvilSampleAndroidLocationProvider.kt:11: Cannot provide: software.amazon.lastmile.kotlin.inject.anvil.sample.LocationProvider e: [ksp] /app/build/generated/ksp/android/androidDebug/kotlin/amazon/lastmile/inject/SoftwareAmazonLastmileKotlin InjectAnvilSampleNetworkLocationProvider.kt:11: as it is already provided e: Error occurred in KSP, check log for detail
  144. kotlin-inject-anvil - common errors - duplicate binding > Task :sample:app:kspDebugKotlinAndroid

    FAILED e: [ksp] /app/build/generated/ksp/android/androidDebug/kotlin/amazon/lastmile/inject/SoftwareAmazonLastmileKotlin InjectAnvilSampleAndroidLocationProvider.kt:11: Cannot provide: software.amazon.lastmile.kotlin.inject.anvil.sample.LocationProvider e: [ksp] /app/build/generated/ksp/android/androidDebug/kotlin/amazon/lastmile/inject/SoftwareAmazonLastmileKotlin InjectAnvilSampleNetworkLocationProvider.kt:11: as it is already provided e: Error occurred in KSP, check log for detail
  145. kotlin-inject-anvil - common errors - duplicate binding > Task :sample:app:kspDebugKotlinAndroid

    FAILED e: [ksp] /app/build/generated/ksp/android/androidDebug/kotlin/amazon/lastmile/inject/SoftwareAmazonLastmileKotlin InjectAnvilSampleAndroidLocationProvider.kt:11: Cannot provide: software.amazon.lastmile.kotlin.inject.anvil.sample.LocationProvider e: [ksp] /app/build/generated/ksp/android/androidDebug/kotlin/amazon/lastmile/inject/SoftwareAmazonLastmileKotlin InjectAnvilSampleNetworkLocationProvider.kt:11: as it is already provided e: Error occurred in KSP, check log for detail
  146. kotlin-inject-anvil - common errors - missing binding > Task :sample:app:kspDebugKotlinAndroid

    FAILED e: [ksp] Cannot find an @Inject constructor or provider for: software.amazon.lastmile.kotlin.inject.anvil.sample.LocationProvider /app/src/androidMain/kotlin/software/amazon/lastmile/kotlin/inject/anvil/sample/AndroidAppComponent.kt:3 4: locationProvider: software.amazon.lastmile.kotlin.inject.anvil.sample.LocationProvider e: Error occurred in KSP, check log for detail
  147. kotlin-inject-anvil - common errors - missing binding > Task :sample:app:kspDebugKotlinAndroid

    FAILED e: [ksp] Cannot find an @Inject constructor or provider for: software.amazon.lastmile.kotlin.inject.anvil.sample.LocationProvider /app/src/androidMain/kotlin/software/amazon/lastmile/kotlin/inject/anvil/sample/AndroidAppComponent.kt:3 4: locationProvider: software.amazon.lastmile.kotlin.inject.anvil.sample.LocationProvider e: Error occurred in KSP, check log for detail
  148. kotlin-inject-anvil - common errors - missing binding > Task :sample:app:kspDebugKotlinAndroid

    FAILED e: [ksp] Cannot find an @Inject constructor or provider for: software.amazon.lastmile.kotlin.inject.anvil.sample.LocationProvider /app/src/androidMain/kotlin/software/amazon/lastmile/kotlin/inject/anvil/sample/AndroidAppComponent.kt:3 4: locationProvider: software.amazon.lastmile.kotlin.inject.anvil.sample.LocationProvider e: Error occurred in KSP, check log for detail
  149. kotlin-inject-anvil - common errors - missing binding > Task :sample:app:kspDebugKotlinAndroid

    FAILED e: [ksp] Cannot find an @Inject constructor or provider for: software.amazon.lastmile.kotlin.inject.anvil.sample.LocationProvider /app/src/androidMain/kotlin/software/amazon/lastmile/kotlin/inject/anvil/sample/AndroidAppComponent.kt:3 4: locationProvider: software.amazon.lastmile.kotlin.inject.anvil.sample.LocationProvider e: Error occurred in KSP, check log for detail • No implementation exists • Missing @Inject • Missing @ContributesBinding • Missing Gradle dependency • Scope mismatch • Wrong qualifier
  150. kotlin-inject-anvil - common errors - circular dependency @Inject @SingleIn(AppScope::class) @ContributesBinding(AppScope::class)

    class NetworkLocationProvider( weatherRepository: WeatherRepository ) : LocationProvider @Inject @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) class WeatherRepositoryImpl( locationProvider: LocationProvider, ) : WeatherRepository
  151. kotlin-inject-anvil - common errors - circular dependency > Task :sample:app:kspDebugKotlinAndroid

    FAILED e: [ksp] Cycle detected /app/src/androidMain/kotlin/software/amazon/lastmile/kotlin/inject/anvil/sample/AndroidAppComponent.kt:5 1: software.amazon.lastmile.kotlin.inject.anvil.sample.WeatherRepositoryImpl(locationProvider: software.amazon.lastmile.kotlin.inject.anvil.sample.LocationProvider) /app/build/generated/ksp/android/androidDebug/kotlin/amazon/lastmile/inject/SoftwareAmazonLastmileKotlin InjectAnvilSampleWeatherRepositoryImpl.kt:11: provideWeatherRepositoryImplWeatherRepository(weatherRepositoryImpl: software.amazon.lastmile.kotlin.inject.anvil.sample.WeatherRepositoryImpl): software.amazon.lastmile.kotlin.inject.anvil.sample.WeatherRepository /app/src/androidMain/kotlin/software/amazon/lastmile/kotlin/inject/anvil/sample/AndroidAppComponent.kt:4 2: software.amazon.lastmile.kotlin.inject.anvil.sample.NetworkLocationProvider(weatherRepository: software.amazon.lastmile.kotlin.inject.anvil.sample.WeatherRepository) /app/build/generated/ksp/android/androidDebug/kotlin/amazon/lastmile/inject/SoftwareAmazonLastmileKotlin InjectAnvilSampleNetworkLocationProvider.kt:11: provideNetworkLocationProviderLocationProvider(networkLocationProvider: software.amazon.lastmile.kotlin.inject.anvil.sample.NetworkLocationProvider): software.amazon.lastmile.kotlin.inject.anvil.sample.LocationProvider /app/src/androidMain/kotlin/software/amazon/lastmile/kotlin/inject/anvil/sample/AndroidAppComponent.kt:3 4: locationProvider: software.amazon.lastmile.kotlin.inject.anvil.sample.LocationProvider e: Error occurred in KSP, check log for detail
  152. kotlin-inject-anvil - common errors - circular dependency > Task :sample:app:kspDebugKotlinAndroid

    FAILED e: [ksp] Cycle detected /app/src/androidMain/kotlin/software/amazon/lastmile/kotlin/inject/anvil/sample/AndroidAppComponent.kt:5 1: software.amazon.lastmile.kotlin.inject.anvil.sample.WeatherRepositoryImpl(locationProvider: software.amazon.lastmile.kotlin.inject.anvil.sample.LocationProvider) /app/build/generated/ksp/android/androidDebug/kotlin/amazon/lastmile/inject/SoftwareAmazonLastmileKotlin InjectAnvilSampleWeatherRepositoryImpl.kt:11: provideWeatherRepositoryImplWeatherRepository(weatherRepositoryImpl: software.amazon.lastmile.kotlin.inject.anvil.sample.WeatherRepositoryImpl): software.amazon.lastmile.kotlin.inject.anvil.sample.WeatherRepository /app/src/androidMain/kotlin/software/amazon/lastmile/kotlin/inject/anvil/sample/AndroidAppComponent.kt:4 2: software.amazon.lastmile.kotlin.inject.anvil.sample.NetworkLocationProvider(weatherRepository: software.amazon.lastmile.kotlin.inject.anvil.sample.WeatherRepository) /app/build/generated/ksp/android/androidDebug/kotlin/amazon/lastmile/inject/SoftwareAmazonLastmileKotlin InjectAnvilSampleNetworkLocationProvider.kt:11: provideNetworkLocationProviderLocationProvider(networkLocationProvider: software.amazon.lastmile.kotlin.inject.anvil.sample.NetworkLocationProvider): software.amazon.lastmile.kotlin.inject.anvil.sample.LocationProvider /app/src/androidMain/kotlin/software/amazon/lastmile/kotlin/inject/anvil/sample/AndroidAppComponent.kt:3 4: locationProvider: software.amazon.lastmile.kotlin.inject.anvil.sample.LocationProvider e: Error occurred in KSP, check log for detail
  153. kotlin-inject-anvil - common errors - circular dependency @Inject @SingleIn(AppScope::class) @ContributesBinding(AppScope::class)

    class NetworkLocationProvider( weatherRepository: WeatherRepository ) : LocationProvider @Inject @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) class WeatherRepositoryImpl( locationProvider: LocationProvider, ) : WeatherRepository
  154. kotlin-inject-anvil - common errors - circular dependency @Inject @SingleIn(AppScope::class) @ContributesBinding(AppScope::class)

    class NetworkLocationProvider( weatherRepository: WeatherRepository ) : LocationProvider @Inject @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) class WeatherRepositoryImpl( locationProvider: () -> LocationProvider, ) : WeatherRepository
  155. kotlin-inject-anvil - common errors - circular dependency @Inject @SingleIn(AppScope::class) @ContributesBinding(AppScope::class)

    class NetworkLocationProvider( weatherRepository: WeatherRepository ) : LocationProvider @Inject @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) class WeatherRepositoryImpl( locationProvider: () -> LocationProvider, ) : WeatherRepository
  156. Summary • Dependency inversion and dependency injection help scale Kotlin

    Multiplatform applications • Manual dependency injection is possible, but requires a lot of maintenance • Compile time dependency injection framework are not magic • kotlin-inject-anvil is an efficient and flexible solution for Kotlin Multiplatform