Slide 1

Slide 1 text

Dagger + Anvil Learning to Love Dependency Injection Ralf Wondratschek @vRallev Gabriel Peal @gpeal8

Slide 2

Slide 2 text

Dagger 2 Refresher - Basic @Singleton @Component interface AppComponent class MainApplication : Application() { lateinit var component : AppComponent override fun onCreate () { super.onCreate() component = DaggerAppComponent.create() } } :app @Singleton class WeatherRepository @Inject constructor() { suspend fun getForecast(): List = TODO() } :feature.weather

Slide 3

Slide 3 text

Dagger 2 Refresher - Interface + Impl @Singleton @Component(modules = [WeatherModule::class]) interface AppComponent interface WeatherRepository { suspend fun getForecast(): List } :lib.weather @Singleton class WeatherRepositoryImpl @Inject constructor () : WeatherRepository { override suspend fun getForecast (): List = TODO() } @Module abstract class WeatherModule { @Binds abstract fun bindsWeatherRepository (r: WeatherRepositoryImpl ): WeatherRepository } :app :feature.weather

Slide 4

Slide 4 text

Dagger 2 Refresher - Binding Interface @Singleton @Component interface AppComponent : WeatherBindings interface WeatherBindings { fun inject(fragment: WeatherFragment) fun weatherRepository(): WeatherRepository } :app :feature.weather

Slide 5

Slide 5 text

Dagger 2 Refresher - Multibindings @Singleton @Component interface AppComponent :app interface WeatherDataSource { suspend fun getForecast (): List } class WeatherChannelDataSource @Inject constructor () : WeatherDataSource { override suspend fun getForecast (): List = TODO() } class WeatherUndergroundDataSource @Inject constructor (): WeatherDataSource { override suspend fun getForecast (): List = TODO() } @Singleton class WeatherRepository @Inject constructor ( private val userPrefs: UserPrefs, private val dataSources : Map, ) { suspend fun getForecast (): List = TODO() } @Module abstract class WeatherModule { @Binds @IntoMap @StringKey ("WeatherChannel" ) abstract fun bindsWeatherChannel (s: WeatherChannelDataSource ): WeatherDataSource @Binds @IntoMap @StringKey ("WeatherUnderground" ) abstract fun bindsWeatherUnderground (s: WeatherUndergroundDataSource ): WeatherDataSource } :feature.weather (modules = [WeatherModule::class])

Slide 6

Slide 6 text

Dagger 2 ● Why it’s good ○ Build time guarantee ○ Fast at runtime (no reflection) ○ Huge community (Stack Overflow/Blog posts galore) ○ You can get it to do just about anything if you need it

Slide 7

Slide 7 text

Dagger 2 ● Why it’s not so good ○ Boilerplate ○ Complicated to learn (names aren’t great) ○ Build time (kapt)

Slide 8

Slide 8 text

Why Anvil? ● Opinionated module structure ● Hundreds of demo applications → Boilerplate code became a problem

Slide 9

Slide 9 text

Why Anvil? :app.1 @Component( WeatherModule, LocationModule ) AppComponent :app.2 @Component( LocationModule, UserModule ) AppComponent :app.3 @Component( LocationModule, UserModule ) AppComponent :feature.1 WeatherModule :feature.2 LocationModule :feature.3 UserModule

Slide 10

Slide 10 text

Why Anvil? Try to build :demo app Add missing dependencies in build.gradle file Sync :demo module in Android Studio Add Dagger modules to Dagger components

Slide 11

Slide 11 text

Why Anvil? Try to build :demo app Add missing dependencies in build.gradle file Sync :demo module in Android Studio Add Dagger modules to Dagger components

Slide 12

Slide 12 text

Why Anvil? @Singleton @Component(modules = [WeatherModule::class]) interface AppComponent

Slide 13

Slide 13 text

Why Anvil? @Singleton @Component(modules = [WeatherModule::class]) interface AppComponent

Slide 14

Slide 14 text

Why Anvil? ● A Dagger module must know to which component it belongs ● A Dagger component must know which modules it needs to include → The scope is the shared connection

Slide 15

Slide 15 text

Why Anvil? @Component(modules = [WeatherModule::class]) interface AppComponent @Module object WeatherModule

Slide 16

Slide 16 text

Why Anvil? @MergeComponent(AppScope::class) interface AppComponent @Module @ContributesTo(AppScope::class) object WeatherModule

Slide 17

Slide 17 text

Why Anvil? @MergeComponent(AppScope::class) interface AppComponent @Module @ContributesTo(AppScope::class) object WeatherModule

Slide 18

Slide 18 text

Why Anvil? :app.1 @Component( WeatherModule, LocationModule ) AppComponent :app.2 @Component( LocationModule, UserModule ) AppComponent :app.3 @Component( LocationModule, UserModule ) AppComponent :feature.1 WeatherModule :feature.2 LocationModule :feature.3 UserModule

Slide 19

Slide 19 text

Why Anvil? :app.1 @MergeComponent(AppScope) AppComponent :app.2 @MergeComponent(AppScope) AppComponent :app.3 @MergeComponent(AppScope) AppComponent :feature.1 @ContributesTo(AppScope) WeatherModule :feature.2 @ContributesTo(AppScope) LocationModule :feature.3 @ContributesTo(AppScope) UserModule

Slide 20

Slide 20 text

Why Anvil? :app.1 @MergeComponent(AppScope) AppComponent :app.2 @MergeComponent(AppScope) AppComponent :app.3 @MergeComponent(AppScope) AppComponent :feature.1 @ContributesTo(AppScope) WeatherModule :feature.2 @ContributesTo(AppScope) LocationModule :feature.3 @ContributesTo(AppScope) UserModule :core object AppScope

Slide 21

Slide 21 text

Why Anvil? ● Allows you to construct the dependency graph in a loose way ● Aligns the build graph with the dependency graph ● Moves decision which scope to use to the declaration side ● Allows you to add new Dagger modules easily to all apps ● Creating new applications is faster

Slide 22

Slide 22 text

Dagger 2 - Basic @Singleton @Component interface AppComponent class MainApplication : Application() { lateinit var component : AppComponent override fun onCreate () { super.onCreate() component = DaggerAppComponent.create() } } :app @Singleton class WeatherRepository @Inject constructor() { suspend fun getForecast(): List = TODO() } :feature.weather

Slide 23

Slide 23 text

Anvil - Basic @Singleton @MergeComponent(AppScope::class) interface AppComponent class MainApplication : Application() { lateinit var component : AppComponent override fun onCreate () { super.onCreate() component = DaggerAppComponent.create() } } @Singleton class WeatherRepository @Inject constructor() { suspend fun getForecast(): List = TODO() } interface AppScope :app :feature.weather :lib.scopes

Slide 24

Slide 24 text

Anvil - Basic @Singleton @MergeComponent(AppScope::class) interface AppComponent Two different names to memorize

Slide 25

Slide 25 text

Anvil - Basic @SingleIn(AppScope::class) @MergeComponent(AppScope::class) interface AppComponent Two different names to memorize

Slide 26

Slide 26 text

Anvil - Basic @Singleton @MergeComponent(AppScope::class) interface AppComponent class MainApplication : Application() { lateinit var component: AppComponent override fun onCreate() { super.onCreate() component = DaggerAppComponent.create() } } @Singleton class WeatherRepository @Inject constructor() { suspend fun getForecast(): List = TODO() } interface AppScope :app :feature.weather :lib.scopes @Scope @Retention(AnnotationRetention.RUNTIME) annotation class SingleIn(val clazz: KClass<*>)

Slide 27

Slide 27 text

Anvil - Basic @SingleIn(AppScope::class) @MergeComponent(AppScope::class) interface AppComponent class MainApplication : Application() { lateinit var component: AppComponent override fun onCreate() { super.onCreate() component = DaggerAppComponent.create() } } @SingleIn(AppScope::class) class WeatherRepository @Inject constructor() { suspend fun getForecast(): List = TODO() } interface AppScope :app :feature.weather :lib.scopes @Scope @Retention(AnnotationRetention.RUNTIME) annotation class SingleIn(val clazz: KClass<*>)

Slide 28

Slide 28 text

Dagger 2 - Interface + Impl @Singleton @Component(modules = [WeatherModule::class]) interface AppComponent interface WeatherRepository { suspend fun getForecast(): List } :lib.weather @Singleton class WeatherRepositoryImpl @Inject constructor () : WeatherRepository { override suspend fun getForecast (): List = TODO() } @Module abstract class WeatherModule { @Binds abstract fun bindsWeatherRepository (r: WeatherRepositoryImpl ): WeatherRepository } :app :feature.weather BOILERPLATE 🔥

Slide 29

Slide 29 text

Anvil - Interface + Impl @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) class WeatherRepositoryImpl @Inject constructor() : WeatherRepository { override suspend fun getForecast(): List = TODO() } :lib.weather :feature.weather interface WeatherRepository { suspend fun getForecast(): List } @Singleton @MergeComponent(AppScope::class) interface AppComponent :app

Slide 30

Slide 30 text

Dagger 2 - Binding Interface @Singleton @Component interface AppComponent : WeatherBindings interface WeatherBindings { fun inject(fragment: WeatherFragment) fun weatherRepository(): WeatherRepository } :app :feature.weather BOILERPLATE 🔥

Slide 31

Slide 31 text

Anvil - Binding Interface @ContributesTo(AppScope::class) interface WeatherBindings { fun inject(fragment: WeatherFragment) fun weatherRepository(): WeatherRepository } :feature.weather @Singleton @MergeComponent(AppScope::class) interface AppComponent :app

Slide 32

Slide 32 text

Dagger 2 - Multibindings @Singleton @Component(modules = [WeatherModule::class]) interface AppComponent interface WeatherDataSource { suspend fun getForecast (): List } class WeatherChannelDataSource @Inject constructor () : WeatherDataSource { override suspend fun getForecast (): List = TODO() } class WeatherUndergroundDataSource @Inject constructor (): WeatherDataSource { override suspend fun getForecast (): List = TODO() } @Singleton class WeatherRepository @Inject constructor ( private val userPrefs: UserPrefs, private val dataSources : Map, ) { suspend fun getForecast (): List = TODO() } @Module abstract class WeatherModule { @Binds @IntoMap @StringKey ("WeatherChannel" ) abstract fun bindsWeatherChannel (s: WeatherChannelDataSource ): WeatherDataSource @Binds @IntoMap @StringKey ("WeatherUnderground" ) abstract fun bindsWeatherUnderground (s: WeatherUndergroundDataSource ): WeatherDataSource } :app :feature.weather BOILERPLATE 🔥 BOILERPLATE 🔥

Slide 33

Slide 33 text

Anvil - Multibindings @Singleton @MergeComponent(AppScope::class) interface AppComponent :app :feature.weather interface WeatherDataSource { suspend fun getForecast(): List } @StringKey("WeatherChannel") @ContributesMultibinding(AppScope::class) class WeatherChannelDataSource @Inject constructor() : WeatherDataSource { override suspend fun getForecast(): List = TODO() } @StringKey("WeatherUnderground") @ContributesMultibinding(AppScope::class) class WeatherUndergroundDataSource @Inject constructor(): WeatherDataSource { override suspend fun getForecast(): List = TODO() } @Singleton class WeatherRepository @Inject constructor( private val userPrefs: UserPrefs, private val dataSources: Map, ) { suspend fun getForecast(): List = TODO() }

Slide 34

Slide 34 text

/* :app /src/main */ @SingleIn(AppScope::class) @MergeComponent(AppScope::class) interface AppComponent open class MainApplication : Application() { lateinit var daggerComponent: Any override fun onCreate() { super.onCreate() daggerComponent = DaggerAppComponent.create() } } Anvil - Replace dependencies in tests

Slide 35

Slide 35 text

/* :app /src/androidTest */ @SingleIn(AppScope::class) @MergeComponent(AppScope::class) interface TestAppComponent class TestApplication : MainApplication() { override lateinit var daggerComponent: Any override fun onCreate() { super.onCreate() daggerComponent = DaggerTestAppComponent.create() } } Anvil - Replace dependencies in tests

Slide 36

Slide 36 text

/* :lib.weatherdata /src/main */ interface WeatherRepository { suspend fun getForecast(): List } /* :features.weatherdata /src/main */ @SingleIn(WeatherScope::class) @ContributesBinding(WeatherScope::class) class WeatherRepositoryImpl @Inject constructor() : WeatherRepository Anvil - Replace dependencies in tests

Slide 37

Slide 37 text

/* :lib.weatherdata /src/main */ interface WeatherRepository { suspend fun getForecast(): List } /* :app /src/androidTest */ @ContributesBinding( scope = WeatherScope::class ) object FakeWeatherRepository : WeatherRepository { override suspend fun getForecast(): List = listOf(1, 2, 3, 4, 5) } Anvil - Replace dependencies in tests

Slide 38

Slide 38 text

/* :lib.weatherdata /src/main */ interface WeatherRepository { suspend fun getForecast(): List } /* :app /src/androidTest */ @ContributesBinding( scope = WeatherScope::class, replaces = [WeatherRepositoryImpl::class] ) object FakeWeatherRepository : WeatherRepository { override suspend fun getForecast(): List = listOf(1, 2, 3, 4, 5) } Anvil - Replace dependencies in tests

Slide 39

Slide 39 text

/* :lib.weatherdata /src/main */ interface WeatherRepository { suspend fun getForecast(): List } /* :app /src/androidTest */ @ContributesBinding( scope = WeatherScope::class, replaces = [WeatherRepositoryImpl::class] ) object FakeWeatherRepository : WeatherRepository { override suspend fun getForecast(): List = listOf(1, 2, 3, 4, 5) } Anvil - Replace dependencies in tests

Slide 40

Slide 40 text

What about Hilt? ● Multi-login ● Guarantee no user information leaks across accounts ● Null safe access to account

Slide 41

Slide 41 text

What about Hilt? class WorkoutFragment : Fragment( R.layout.workout_fragment), DaggerComponentOwner { // github/gpeal/Anvil-Sample override val daggerComponent : WeatherComponent by fragmentComponent { scope, app -> app.bindings().workoutComponentBuilder().create(scope) } }

Slide 42

Slide 42 text

What about Hilt?

Slide 43

Slide 43 text

What about Hilt?

Slide 44

Slide 44 text

Anvil - Build Time

Slide 45

Slide 45 text

Anvil - Dagger Factory Generation plugins { id 'org.jetbrains.kotlin.kapt' id 'com.squareup.anvil' } dependencies { api "com.google.dagger:dagger:$daggerVersion" kapt "com.google.dagger:dagger-compiler:$daggerVersion" }

Slide 46

Slide 46 text

Anvil - Dagger Factory Generation plugins { id 'com.squareup.anvil' } anvil { generateDaggerFactories = true } dependencies { api "com.google.dagger:dagger:$daggerVersion" }

Slide 47

Slide 47 text

Build time for single modules improved by up to 65% Anvil - Dagger Factory Generation

Slide 48

Slide 48 text

Build time for our full project improved by 16% on average Anvil - Build time

Slide 49

Slide 49 text

Build time for Slack improved by 25% Anvil - Build time

Slide 50

Slide 50 text

TL;DR ● If you use Dagger already, then Anvil will make your build times significantly faster and provide more features on top ○ (For Dagger + Hilt the diff is even bigger) Anvil - Build time

Slide 51

Slide 51 text

Extend Anvil - Custom Code Generator interface WeatherApi { @GET("…") suspend fun getForecast(): Forecast } @Module @ContributesTo(AppScope::class) object WeatherApiModule { @Provides @Reusable fun providesWeatherApi(retrofit: Retrofit): WeatherApi { return retrofit.create() } } @ContributesApi(AppScope::class) Eliminated // github/gpeal/Avil-Sample

Slide 52

Slide 52 text

@Module @ContributesTo(AppScope::class) public abstract class WeatherViewModelModule { @Binds @IntoMap @ViewModelKey(WeatherViewModel::class) public abstract fun bindWeatherViewModelFactory( factory: WeatherViewModel.Factory, ): TonalViewModelFactory<*, *> } Extend Anvil - Custom Code Generator @ContributesViewModel(UserScope::class) class WeatherViewModel @AssistedInject constructor( @Assisted initialState: WeatherState, weatherRepository: WeatherRepository, ) : MavericksViewModel(initialState) { … } Eliminated

Slide 53

Slide 53 text

Extend Anvil - Custom Code Generator @ContributesFeatureFlag enum class MyFeatureFlag( override val flag: String, ) : BaseFeatureFlag { MyFlag("my-flag"), MyFlag2("my-flag-2"), } @Module @ContributesTo(AppScope::class) public abstract class MyFeatureFlagModule { @Provides @ElementsIntoSet fun providesMyFeatureFlags(): Set = MyFeatureFlag.values().toSet() } Eliminated

Slide 54

Slide 54 text

Extend Anvil - Custom Code Generator

Slide 55

Slide 55 text

Thank you! Ralf Wondratschek @vRallev Gabriel Peal @gpeal8 github.com/gpeal/Anvil-Sample

Slide 56

Slide 56 text

Resources ● https://github.com/square/anvil ● https://github.com/gpeal/Anvil-Sample ● https://gpeal.medium.com/dagger-anvil-learning-to-love-d ependency-injection-on-android-8fad3d5530c9