Slide 1

Slide 1 text

Scale your Kotlin Multiplatform projects using dependency injection May 23rd, 2025 Ralf Wondratschek

Slide 2

Slide 2 text

Scale your KMP projects using dependency injection Ralf Wondratschek ralf-wondratschek.com

Slide 3

Slide 3 text

https://www.youtube.com/watch?v=xESX82kwjn4

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

https://www.youtube.com/watch?v=0T_zvUEqsD4 https://www.youtube.com/watch?v=0T_zvUEqsD4

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

Background ● Adopted KMP for new initiatives in 2024 ● Shared APIs ● Shared or distinct implementations ● Common architecture for shared features

Slide 10

Slide 10 text

Background https://amzn.github.io/app-platform

Slide 11

Slide 11 text

Background https://ralf-wondratschek.com/presentations

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

Dagger 2 :weather:impl-phone :weather:impl-van :weather:impl-jvm :weather:public :app:van :app:jvm :app:phone

Slide 14

Slide 14 text

Anvil interface WeatherRepository { fun provideForecast(location: Location): Forecast } @Singleton @ContributesBinding(AppScope::class) class WeatherRepositoryImpl @Inject constructor( client: HttpClient, ) : WeatherRepository

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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.

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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.

Slide 21

Slide 21 text

Dependency Injection class WeatherReport { val weatherRepository: WeatherRepository = WeatherRepositoryImpl() }

Slide 22

Slide 22 text

Dependency Injection class WeatherReport( val weatherRepository: WeatherRepository, ) { ... }

Slide 23

Slide 23 text

Do we need a framework?

Slide 24

Slide 24 text

Do we need a framework? interface WeatherRepository class WeatherRepositoryImpl : WeatherRepository class WeatherReport( val weatherRepository: WeatherRepository, )

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

Do we need a framework? interface LocationProvider class RealTimeLocationProvider : LocationProvider class WeatherRepositoryImpl( locationProvider: LocationProvider, ) : WeatherRepository

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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 }

Slide 31

Slide 31 text

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) ● …

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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())) }

Slide 34

Slide 34 text

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())) }

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

Service locator // :weather-report fun setupWeatherReportDependencies(component: Component) { component.register(WeatherReport::class, WeatherReport(component.get(), component.get())) }

Slide 38

Slide 38 text

Service locator fun showWeatherReport(component: Component) { component.get() }

Slide 39

Slide 39 text

Problems ● No compile time safety ● No circular dependency detection ● Race conditions ● Boilerplate ● Service locator

Slide 40

Slide 40 text

Invoke constructors // :weather-report fun setupWeatherReportDependencies(component: Component) { component.register(WeatherReport::class, WeatherReport(component.get(), component.get())) }

Slide 41

Slide 41 text

Invoke constructors // :weather-report fun setupWeatherReportDependencies(component: Component) { component.register(WeatherReport::class, WeatherReport(component.get(), component.get())) }

Slide 42

Slide 42 text

Invoke constructors dependencies { implementation "org.jetbrains.kotlin:kotlin-reflect" }

Slide 43

Slide 43 text

Invoke constructors @Target(CONSTRUCTOR, CLASS) annotation class Inject

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

Invoke constructors class RealTimeLocationProvider : LocationProvider class WeatherRepositoryImpl( val locationProvider: LocationProvider, ) : WeatherRepository class WeatherReport( val weatherRepository: WeatherRepository, val locationProvider: LocationProvider, )

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

Invoke constructors fun main() { val component = Component() component.get() }

Slide 52

Slide 52 text

Invoke constructors fun main() { val component = Component() component.get() } 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)

Slide 53

Slide 53 text

Invoke constructors fun main() { val component = Component().apply { register(LocationProvider::class, get()) register(WeatherRepository::class, get()) } component.get() }

Slide 54

Slide 54 text

Invoke constructors fun main() { val component = Component().apply { bind() bind() } component.get() }

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

Problems service locator ● No compile time safety ● No circular dependency detection ● Race conditions ● Boilerplate (a little less) ● Service locator ● JVM / Android only ● Runtime overhead

Slide 57

Slide 57 text

Problems service locator object Component { val locationProvider: LocationProvider = RealTimeLocationProvider() val weatherRepository: WeatherRepository = WeatherRepositoryImpl(locationProvider) val weatherReport = WeatherReport(weatherRepository, locationProvider) } fun showWeatherReport() { Component.weatherReport }

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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) }

Slide 64

Slide 64 text

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) }

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

Scopes with shorter lifecycle App lifetime

Slide 68

Slide 68 text

Scopes with shorter lifecycle App Scope App lifetime

Slide 69

Slide 69 text

Scopes with shorter lifecycle App Scope App lifetime Logged-in Scope

Slide 70

Slide 70 text

Scopes with shorter lifecycle App Scope App lifetime Logged-in Scope Logged-in Scope

Slide 71

Slide 71 text

Scopes with shorter lifecycle App Scope App lifetime Logged-in Scope Logged-in Scope On-duty Scope

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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) }

Slide 74

Slide 74 text

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) }

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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) }

Slide 78

Slide 78 text

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) }

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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) }

Slide 82

Slide 82 text

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) }

Slide 83

Slide 83 text

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) }

Slide 84

Slide 84 text

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) }

Slide 85

Slide 85 text

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) }

Slide 86

Slide 86 text

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) }

Slide 87

Slide 87 text

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) }

Slide 88

Slide 88 text

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) }

Slide 89

Slide 89 text

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) }

Slide 90

Slide 90 text

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 }

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

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 }

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

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 }

Slide 98

Slide 98 text

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 }

Slide 99

Slide 99 text

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) }

Slide 100

Slide 100 text

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) }

Slide 101

Slide 101 text

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) }

Slide 102

Slide 102 text

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, )

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

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, )

Slide 106

Slide 106 text

@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

Slide 107

Slide 107 text

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

Slide 108

Slide 108 text

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

Slide 109

Slide 109 text

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

Slide 110

Slide 110 text

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

Slide 111

Slide 111 text

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 = ??? }

Slide 112

Slide 112 text

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 = ??? }

Slide 113

Slide 113 text

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 = ???, ) }

Slide 114

Slide 114 text

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, ) }

Slide 115

Slide 115 text

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, ) }

Slide 116

Slide 116 text

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, ) }

Slide 117

Slide 117 text

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, ) }

Slide 118

Slide 118 text

kotlin-inject-anvil

Slide 119

Slide 119 text

kotlin-inject kotlin-inject is a compile-time dependency injection (DI) framework designed specifically for Kotlin. https://github.com/evant/kotlin-inject

Slide 120

Slide 120 text

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

Slide 121

Slide 121 text

Prototype @Inject @ContributesBinding class RealTimeLocationProvider : LocationProvider @Inject @ContributesBinding(replaces = RealTimeLocationProvider::class) class FakeLocationProvider : LocationProvider @Inject @ContributesBinding class WeatherRepositoryImpl( val locationProvider: LocationProvider, ) : WeatherRepository

Slide 122

Slide 122 text

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

Slide 123

Slide 123 text

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

Slide 124

Slide 124 text

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

Slide 125

Slide 125 text

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

Slide 126

Slide 126 text

kotlin-inject-anvil object LoggedInScope

Slide 127

Slide 127 text

kotlin-inject-anvil object LoggedInScope interface SavedLocations @Inject @SingleIn(LoggedInScope::class) @ContributesBinding(LoggedInScope::class) class SavedLocationsImpl( val locationProvider: LocationProvider, ) : SavedLocations

Slide 128

Slide 128 text

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, )

Slide 129

Slide 129 text

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, )

Slide 130

Slide 130 text

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, )

Slide 131

Slide 131 text

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, )

Slide 132

Slide 132 text

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, )

Slide 133

Slide 133 text

kotlin-inject-anvil // src/commonMain @SingleIn(AppScope::class) @MergeComponent(AppScope::class) interface AppComponent

Slide 134

Slide 134 text

kotlin-inject-anvil // src/commonMain @SingleIn(AppScope::class) @MergeComponent(AppScope::class) interface AppComponent @CreateComponent expect fun KClass.createComponent(): AppComponent

Slide 135

Slide 135 text

kotlin-inject-anvil // src/commonMain @SingleIn(AppScope::class) @MergeComponent(AppScope::class) interface AppComponent @CreateComponent expect fun KClass.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

Slide 136

Slide 136 text

kotlin-inject-anvil fun main() { val appComponent = AppComponent::class.createComponent() }

Slide 137

Slide 137 text

kotlin-inject-anvil - features ● Provider methods ● Qualifiers ● Multi-bindings ● Assisted injection ● Lazy injection ● Default argument support ● Overridable provider methods ● Child components ● Component arguments

Slide 138

Slide 138 text

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

Slide 139

Slide 139 text

kotlin-inject-anvil - critique ● No wide adoption ● Slower build times compared to vanilla Kotlin due to KSP ● Learning curve ● Unclear error messages

Slide 140

Slide 140 text

kotlin-inject-anvil - our setup

Slide 141

Slide 141 text

kotlin-inject-anvil - our setup https://amzn.github.io/app-platform

Slide 142

Slide 142 text

kotlin-inject-anvil - our setup :phone-android-app

Slide 143

Slide 143 text

kotlin-inject-anvil - our setup :phone-android-app :phone-ios-umbrella /iosPhoneProject

Slide 144

Slide 144 text

kotlin-inject-anvil - our setup :phone-android-app :van-app :phone-ios-umbrella /iosPhoneProject

Slide 145

Slide 145 text

kotlin-inject-anvil - our setup :phone-android-app :van-app :phone-ios-umbrella /iosPhoneProject :demo-app /iosDemoProject

Slide 146

Slide 146 text

kotlin-inject-anvil - our setup :phone-android-app :van-app :phone-ios-umbrella /iosPhoneProject :demo-app /iosDemoProject All library modules

Slide 147

Slide 147 text

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

Slide 148

Slide 148 text

:location kotlin-inject-anvil - our setup

Slide 149

Slide 149 text

:location kotlin-inject-anvil - our setup :public commonMain/kotlin/LocationProvider

Slide 150

Slide 150 text

:location kotlin-inject-anvil - our setup :public commonMain/kotlin/LocationProvider :impl commonMain/kotlin/RealTimeLocationProvider

Slide 151

Slide 151 text

:location kotlin-inject-anvil - our setup :public commonMain/kotlin/LocationProvider :impl commonMain/kotlin/RealTimeLocationProvider :weather :public commonMain/kotlin/WeatherRepository :impl commonMain/kotlin/WeatherRepositoryImpl

Slide 152

Slide 152 text

:location kotlin-inject-anvil - our setup :public commonMain/kotlin/LocationProvider :impl commonMain/kotlin/RealTimeLocationProvider :weather :public commonMain/kotlin/WeatherRepository :impl commonMain/kotlin/WeatherRepositoryImpl

Slide 153

Slide 153 text

: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

Slide 154

Slide 154 text

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

Slide 155

Slide 155 text

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

Slide 156

Slide 156 text

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

Slide 157

Slide 157 text

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

Slide 158

Slide 158 text

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

Slide 159

Slide 159 text

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

Slide 160

Slide 160 text

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

Slide 161

Slide 161 text

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

Slide 162

Slide 162 text

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

Slide 163

Slide 163 text

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

Slide 164

Slide 164 text

kotlin-inject-anvil - constructor injection // weather/impl/src/commonMain/kotlin @Inject @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) class WeatherRepositoryImpl : WeatherRepository

Slide 165

Slide 165 text

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

Slide 166

Slide 166 text

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

Slide 167

Slide 167 text

kotlin-inject-anvil - constructor injection interface NavigationManager { val screen: StateFlow 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 override fun goTo(screen: Screen) { _screen.update { screen } } }

Slide 168

Slide 168 text

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

Slide 169

Slide 169 text

kotlin-inject-anvil - constructor injection @Inject class NavigationPresenter( private val navigationManager: NavigationManager, private val lazyCounterPresenter: () -> CounterPresenter, private val lazyItemPresenter: () -> ItemListDetailPresenter, ) : MoleculePresenter { @Composable override fun present(input: Unit): Template { ... } }

Slide 170

Slide 170 text

kotlin-inject-anvil - constructor injection @ContributesRenderer class ComposeCounterRenderer : ComposeRenderer() { @Composable override fun Compose(model: Model) { ... } }

Slide 171

Slide 171 text

kotlin-inject-anvil - constructor injection @ContributesRobot(AppScope::class) class ComposeCounterRobot : ComposeRobot() { fun seeCounterView() { ... } fun counterIsIncrementing() { ... } fun clickCounterText() { ... } }

Slide 172

Slide 172 text

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

Slide 173

Slide 173 text

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

Slide 174

Slide 174 text

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

Slide 175

Slide 175 text

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

Slide 176

Slide 176 text

kotlin-inject-anvil - provide Android dependencies // androidApp/src/androidMain/kotlin @SingleIn(AppScope::class) @MergeComponent(AppScope::class) abstract class AndroidAppComponent( @get:Provides val application: Application, )

Slide 177

Slide 177 text

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()) }

Slide 178

Slide 178 text

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.createComponent(uiApplication: UIApplication): IosAppComponent

Slide 179

Slide 179 text

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.createComponent(uiApplication: UIApplication): IosAppComponent interface IosNativeImplementations { val uiApplication: UIApplication @ContributesTo(AppScope::class) interface Component { @Provides fun provideUiApplication( iosNativeImplementations: IosNativeImplementations, ): UIApplication = iosNativeImplementations.uiApplication } }

Slide 180

Slide 180 text

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.createComponent(uiApplication: UIApplication): IosAppComponent interface IosNativeImplementations { val uiApplication: UIApplication @ContributesTo(AppScope::class) interface Component { @Provides fun provideUiApplication( iosNativeImplementations: IosNativeImplementations, ): UIApplication = iosNativeImplementations.uiApplication } }

Slide 181

Slide 181 text

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.createComponent(uiApplication: UIApplication): IosAppComponent interface IosNativeImplementations { val uiApplication: UIApplication @ContributesTo(AppScope::class) interface Component { @Provides fun provideUiApplication( iosNativeImplementations: IosNativeImplementations, ): UIApplication = iosNativeImplementations.uiApplication } }

Slide 182

Slide 182 text

kotlin-inject-anvil - extensions https://github.com/amzn/kotlin-inject-anvil?tab=readme-ov-file#custom-symbol-processors

Slide 183

Slide 183 text

kotlin-inject-anvil - extensions class ComposeCounterRenderer : ComposeRenderer() { }

Slide 184

Slide 184 text

kotlin-inject-anvil - extensions @Inject class ComposeCounterRenderer : ComposeRenderer() { @ContributesTo(RendererScope::class) interface Component { @Provides @IntoMap fun provideComposeCounterRenderer( renderer: () -> ComposeCounterRenderer, ): Pair, () -> Renderer<*>> = Model::class to renderer } }

Slide 185

Slide 185 text

kotlin-inject-anvil - extensions @ContributesRenderer class ComposeCounterRenderer : ComposeRenderer() { ... }

Slide 186

Slide 186 text

kotlin-inject-anvil - extensions class ComposeCounterRobot : ComposeRobot() { }

Slide 187

Slide 187 text

kotlin-inject-anvil - extensions @Inject class ComposeCounterRobot : ComposeRobot() { @ContributesTo(AppScope::class) interface Component { @Provides @IntoMap fun provideComposeCounterRobot( robot: () -> ComposeCounterRobot, ): Pair, () -> Robot> = ComposeCounterRobot::class to robot } }

Slide 188

Slide 188 text

kotlin-inject-anvil - extensions @ContributesRobot(AppScope::class) class ComposeCounterRobot : ComposeRobot() { ... }

Slide 189

Slide 189 text

kotlin-inject-anvil - extensions interface Scoped { fun onEnterScope(scope: Scope) = Unit fun onExitScope() = Unit }

Slide 190

Slide 190 text

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

Slide 191

Slide 191 text

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

Slide 192

Slide 192 text

kotlin-inject-anvil - expect-actual obsolete // Single :location module // location/src/commonMain/kotlin interface LocationProvider

Slide 193

Slide 193 text

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

Slide 194

Slide 194 text

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

Slide 195

Slide 195 text

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

Slide 196

Slide 196 text

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

Slide 197

Slide 197 text

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

Slide 198

Slide 198 text

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

Slide 199

Slide 199 text

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

Slide 200

Slide 200 text

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

Slide 201

Slide 201 text

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

Slide 202

Slide 202 text

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

Slide 203

Slide 203 text

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

Slide 204

Slide 204 text

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

Slide 205

Slide 205 text

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

Slide 206

Slide 206 text

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

Slide 207

Slide 207 text

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

Slide 208

Slide 208 text

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

Slide 209

Slide 209 text

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

Slide 210

Slide 210 text

The next step

Slide 211

Slide 211 text

The next step https://zacsweers.github.io/metro

Slide 212

Slide 212 text

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

Slide 213

Slide 213 text

Thank You, and Donʼt Forget to Vote ralf-wondratschek.com

Slide 214

Slide 214 text

Scale your Kotlin Multiplatform projects using dependency injection Ralf Wondratschek