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

Dagger Party Tricks

Zac Sweers
February 19, 2019

Dagger Party Tricks

Neat things you can do with dagger and common libraries/patterns in modern android development.

Given at Londroid on 2019/02/19 and Droidcon NYC on 2019/08/26

Zac Sweers

February 19, 2019
Tweet

More Decks by Zac Sweers

Other Decks in Programming

Transcript

  1. Dagger Party Tricks
    @ZacSweers

    View full-size slide

  2. Dagger Party Tricks

    View full-size slide

  3. 4ish Dagger Party Tricks

    View full-size slide

  4. Deferred
    Initialization

    View full-size slide

  5. @Provides fun provideRetrofit(): Retrofit {
    return Retrofit.Builder()
    .baseUrl("https://example.com")
    .build()
    }a

    View full-size slide

  6. @Provides fun provideClient(): OkHttpClient {
    return OkHttpClient.Builder()
    .build()
    }b
    @Provides fun provideRetrofit(client: OkHttpClient): Retrofit {
    return Retrofit.Builder()
    .client(client)
    .build()
    }a

    View full-size slide

  7. @Provides fun provideCache(ctx: Context): Cache {
    return Cache(ctx.cacheDir, CACHE_SIZE)
    }c
    @Provides fun provideClient(cache: Cache): OkHttpClient {
    return OkHttpClient.Builder()
    .cache(cache)
    .build()
    }b
    @Provides fun provideRetrofit(client: OkHttpClient): Retrofit {
    return Retrofit.Builder()
    .client(client)
    .build()
    }a

    View full-size slide

  8. @Provides fun provideCache(ctx: Context): Cache {
    return Cache(ctx.cacheDir, CACHE_SIZE)
    }c
    @Provides fun provideClient(cache: Cache): OkHttpClient {
    return OkHttpClient.Builder()
    .cache(cache)
    .build()
    }b
    @Provides fun provideRetrofit(client: OkHttpClient): Retrofit {
    return Retrofit.Builder()
    .client(client)
    .build()
    }a

    View full-size slide

  9. Courtesy of Nick Butcher (@crafty)
    @Provides fun provideCache(ctx: Context): Cache {
    return Cache(ctx.cacheDir, CACHE_SIZE)
    }c
    @Provides fun provideClient(cache: Cache): OkHttpClient {
    return OkHttpClient.Builder()
    .cache(cache)
    .build()
    }b
    @Provides fun provideRetrofit(client: OkHttpClient): Retrofit {
    return Retrofit.Builder()
    .client(client)
    .build()
    }a
    @Inject
    constructor(
    retrofit: Retrofit
    )e

    View full-size slide

  10. Courtesy of Nick Butcher (@crafty)
    @Provides fun provideCache(ctx: Context): Cache {
    return Cache(ctx.cacheDir, CACHE_SIZE)
    }c
    @Provides fun provideClient(cache: Cache): OkHttpClient {
    return OkHttpClient.Builder()
    .cache(cache)
    .build()
    }b
    @Provides fun provideRetrofit(client: OkHttpClient): Retrofit {
    return Retrofit.Builder()
    .client(client)
    .build()
    }a
    @Inject
    constructor(
    retrofit: Retrofit
    )e

    View full-size slide

  11. Courtesy of Nick Butcher (@crafty)
    @Provides fun provideCache(ctx: Context): Cache {
    return Cache(ctx.cacheDir, CACHE_SIZE)
    }c
    @Provides fun provideClient(cache: Cache): OkHttpClient {
    return OkHttpClient.Builder()
    .cache(cache)
    .build()
    }b
    @Provides fun provideRetrofit(client: OkHttpClient): Retrofit {
    return Retrofit.Builder()
    .client(client)
    .build()
    }a
    @Inject
    constructor(
    retrofit: Retrofit
    )e

    View full-size slide

  12. Courtesy of Nick Butcher (@crafty)
    @Provides fun provideCache(ctx: Context): Cache {
    return Cache(ctx.cacheDir, CACHE_SIZE)
    }c
    @Provides fun provideClient(cache: Cache): OkHttpClient {
    return OkHttpClient.Builder()
    .cache(cache)
    .build()
    }b
    @Provides fun provideRetrofit(client: OkHttpClient): Retrofit {
    return Retrofit.Builder()
    .client(client)
    .build()
    }a
    @Inject
    constructor(
    retrofit: Retrofit
    )e

    View full-size slide

  13. Courtesy of Nick Butcher (@crafty)
    @Provides fun provideCache(ctx: Context): Cache {
    return Cache(ctx.cacheDir, CACHE_SIZE)
    }c
    @Provides fun provideClient(cache: Cache): OkHttpClient {
    return OkHttpClient.Builder()
    .cache(cache)
    .build()
    }b
    @Provides fun provideRetrofit(client: OkHttpClient): Retrofit {
    return Retrofit.Builder()
    .client(client)
    .build()
    }a
    100-150ms!
    @Inject
    constructor(
    retrofit: Retrofit
    )e

    View full-size slide

  14. Courtesy of Nick Butcher (@crafty)
    @Provides fun provideCache(ctx: Context): Cache {
    return Cache(ctx.cacheDir, CACHE_SIZE)
    }c
    @Provides fun provideClient(cache: Cache): OkHttpClient {
    return OkHttpClient.Builder()
    .cache(cache)
    .build()
    }b
    @Provides fun provideRetrofit(client: OkHttpClient): Retrofit {
    return Retrofit.Builder()
    .client(client)
    .build()
    }a
    100-150ms!
    @Inject
    constructor(
    retrofit: Retrofit
    )e

    View full-size slide

  15. Courtesy of Nick Butcher (@crafty)
    @Provides fun provideCache(ctx: Context): Cache {
    return Cache(ctx.cacheDir, CACHE_SIZE)
    }c
    @Provides fun provideClient(cache: Cache): OkHttpClient {
    return OkHttpClient.Builder()
    .cache(cache)
    .build()
    }b
    @Provides fun provideRetrofit(client: OkHttpClient): Retrofit {
    return Retrofit.Builder()
    .client(client)
    .build()
    }a
    @Inject
    constructor(
    retrofit: Lazy
    )e
    100-150ms!

    View full-size slide

  16. Courtesy of Nick Butcher (@crafty)
    @Provides fun provideCache(ctx: Context): Cache {
    return Cache(ctx.cacheDir, CACHE_SIZE)
    }c
    @Provides fun provideClient(cache: Cache): OkHttpClient {
    return OkHttpClient.Builder()
    .cache(cache)
    .build()
    }b
    @Provides fun provideRetrofit(client: OkHttpClient): Retrofit {
    return Retrofit.Builder()
    .client(client)
    .build()
    }a
    @Inject
    constructor(
    retrofit: Lazy
    )e
    100-150ms!
    retrofit.get().create(...)

    View full-size slide

  17. Courtesy of Nick Butcher (@crafty)
    @Provides fun provideCache(ctx: Context): Cache {
    return Cache(ctx.cacheDir, CACHE_SIZE)
    }c
    @Provides fun provideClient(cache: Cache): OkHttpClient {
    return OkHttpClient.Builder()
    .cache(cache)
    .build()
    }b
    @Provides fun provideRetrofit(client: OkHttpClient): Retrofit {
    return Retrofit.Builder()
    .client(client)
    .build()
    }a
    @Inject
    constructor(
    retrofit: Lazy
    )e
    100-150ms!
    retrofit.get().create(...)

    View full-size slide

  18. @Provides fun provideRetrofit(client: OkHttpClient): Retrofit {
    return Retrofit.Builder()
    .client(client)
    .build()
    }a

    View full-size slide

  19. @Provides fun provideRetrofit(client: OkHttpClient): Retrofit {
    return Retrofit.Builder()
    .callFactory(client)
    .build()
    }a

    View full-size slide

  20. @Provides fun provideRetrofit(client: OkHttpClient): Retrofit {
    return Retrofit.Builder()
    .callFactory(client)
    .build()
    }a
    Call.Factory

    View full-size slide

  21. @Provides fun provideRetrofit(client: OkHttpClient): Retrofit {
    return Retrofit.Builder()
    .callFactory(object : Call.Factory by client {})
    .build()
    }a

    View full-size slide

  22. @Provides fun provideRetrofit(client: OkHttpClient): Retrofit {
    return Retrofit.Builder()
    .callFactory { client.newCall(it) }
    .build()
    }a

    View full-size slide

  23. @Provides fun provideRetrofit(client: Lazy): Retrofit {
    return Retrofit.Builder()
    .callFactory { client.get().newCall(it) }
    .build()
    }a

    View full-size slide

  24. @Provides fun provideRetrofit(client: Lazy): Retrofit {
    return Retrofit.Builder()
    .callFactory { client.get().newCall(it) }
    .build()
    }a

    View full-size slide

  25. Courtesy of Nick Butcher (@crafty)
    @Provides fun provideCache(ctx: Context): Cache {
    return Cache(ctx.cacheDir, CACHE_SIZE)
    }c
    @Provides fun provideClient(cache: Cache): OkHttpClient {
    return OkHttpClient.Builder()
    .cache(cache)
    .build()
    }b
    @Provides fun provideRetrofit(client: OkHttpClient): Retrofit {
    return Retrofit.Builder()
    .client(client)
    .build()
    }a
    @Inject
    constructor(
    retrofit: Lazy
    )e
    100-150ms!
    retrofit.get().create(...)

    View full-size slide

  26. Courtesy of Nick Butcher (@crafty)
    @Provides fun provideCache(ctx: Context): Cache {
    return Cache(ctx.cacheDir, CACHE_SIZE)
    }c
    @Provides fun provideClient(cache: Cache): OkHttpClient {
    return OkHttpClient.Builder()
    .cache(cache)
    .build()
    }b
    @Provides fun provideRetrofit(client: OkHttpClient): Retrofit {
    return Retrofit.Builder()
    .client(client)
    .build()
    }a
    @Inject
    constructor(
    retrofit: Lazy
    )e
    100-150ms!
    retrofit.get().create(...)
    Main thread!

    View full-size slide

  27. Courtesy of Nick Butcher (@crafty)
    @Provides fun provideCache(ctx: Context): Cache {
    return Cache(ctx.cacheDir, CACHE_SIZE)
    }c
    @Provides fun provideClient(cache: Cache): OkHttpClient {
    return OkHttpClient.Builder()
    .cache(cache)
    .build()
    }b
    @Provides fun provideRetrofit(
    client: Lazy): Retrofit {
    return Retrofit.Builder()
    .callFactory { client.get().newCall(it) }
    .build()
    }a
    @Inject
    constructor(
    retrofit: Retrofit
    )e
    100-150ms!
    Background thread

    View full-size slide

  28. Courtesy of Nick Butcher (@crafty)
    @Provides fun provideCache(ctx: Context): Cache {
    checkMainThread()
    return Cache(ctx.cacheDir, CACHE_SIZE)
    }c
    @Provides fun provideClient(cache: Cache): OkHttpClient {
    checkMainThread()
    return OkHttpClient.Builder()
    .cache(cache)
    .build()
    }b
    @Provides fun provideRetrofit(
    client: Lazy): Retrofit {
    return Retrofit.Builder()
    .callFactory { client.get().newCall(it) }
    .build()
    }a
    @Inject
    constructor(
    retrofit: Retrofit
    )e
    100-150ms!
    Background thread

    View full-size slide

  29. Modularizing

    View full-size slide

  30. @Module
    class MyGiantModule {
    @Provides
    fun provideSeasoning() = Seasoning()
    @Provides
    fun provideTaco(seasoning: Seasoning) = Taco(seasoning)
    }a

    View full-size slide

  31. @Module
    class MyGiantModuleBase {
    @Provides
    fun provideSeasoning() = Seasoning()
    }a
    @Module(includes = MyGiantModuleBase::class)
    class MyGiantModule {
    @Provides
    fun provideTaco(seasoning: Seasoning) = Taco(seasoning)
    }b

    View full-size slide

  32. @Module(includes = MyGiantModuleBase::class)
    class MyGiantModule {
    @Provides
    fun provideTaco(seasoning: Seasoning) = Taco(seasoning)
    }b
    @Module
    class MyGiantModuleBase {
    @Provides
    fun provideSeasoning() = Seasoning()
    }a

    View full-size slide

  33. @Module
    class MyGiantModule {
    @Provides
    fun provideSeasoning() = Seasoning()
    @Provides
    fun provideTaco(seasoning: Seasoning) = Taco(seasoning)
    }a

    View full-size slide

  34. @Module
    class SeasoningModule {
    @Provides
    fun provideSeasoning() = Seasoning()
    }b
    @Module(includes = SeasoningModule::class)
    class MyGiantModule {
    @Provides
    fun provideTaco(seasoning: Seasoning) = Taco(seasoning)
    }a

    View full-size slide

  35. @Module
    class SeasoningModule {
    @Provides
    fun provideSeasoning() = Seasoning()
    }b
    @Module(includes = [SeasoningModule::class, TacoModule::class])
    class MyGiantModule
    @Module
    class TacoModule {
    @Provides
    fun provideTaco(seasoning: Seasoning) = Taco(seasoning)
    }c

    View full-size slide

  36. @Module
    class SeasoningModule {
    @Provides
    fun provideSeasoning() = Seasoning()
    }b
    @Module(includes = TacoModule::class)
    class MyGiantModule
    @Module(includes = SeasoningModule::class)
    class TacoModule {
    @Provides
    fun provideTaco(seasoning: Seasoning) = Taco(seasoning)
    }c

    View full-size slide

  37. @Module
    class SeasoningModule {
    @Provides
    fun provideSeasoning() = Seasoning()
    }b
    @Module(includes = SeasoningModule::class)
    class TacoModule {
    @Provides
    fun provideTaco(seasoning: Seasoning) = Taco(seasoning)
    }c

    View full-size slide

  38. class Seasoning @Inject constructor()
    class Taco @Inject constructor(seasoning: Seasoning)

    View full-size slide

  39. class Taco @Inject constructor(seasoning: Seasoning)
    Food subproject
    Feature A

    View full-size slide

  40. class Taco @Inject constructor(seasoning: Seasoning)
    Food subproject
    Feature A
    class Taco_Factory

    View full-size slide

  41. class Taco @Inject constructor(seasoning: Seasoning)
    Food subproject
    Feature A
    class Taco_Factory
    Feature B
    class Taco_Factory

    View full-size slide

  42. class Taco @Inject constructor(seasoning: Seasoning)
    Food subproject
    Feature A
    class Taco_Factory
    Feature B
    class Taco_Factory
    App

    View full-size slide

  43. class Taco @Inject constructor(seasoning: Seasoning)
    Food subproject
    Feature A
    class Taco_Factory
    Feature B
    class Taco_Factory
    App

    View full-size slide

  44. class Taco @Inject constructor(seasoning: Seasoning)
    Food subproject
    Feature A
    class Taco_Factory
    Feature B
    App

    View full-size slide

  45. Internal APIs

    View full-size slide

  46. @Module
    class FoodModule {
    @Provides
    fun provideSeasoning() = Seasoning()
    @Provides
    fun provideTaco(seasoning: Seasoning) = Taco(seasoning)
    }

    View full-size slide

  47. @Qualifier
    annotation class InternalApi

    View full-size slide

  48. @Qualifier
    private annotation class InternalApi

    View full-size slide

  49. @Qualifier
    private annotation class InternalApi
    @Module
    class FoodModule {
    @Provides
    fun provideSeasoning() = Seasoning()
    @Provides
    fun provideTaco(seasoning: Seasoning) = Taco(seasoning)
    }a

    View full-size slide

  50. @Qualifier
    private annotation class InternalApi
    @Module
    class FoodModule {
    @InternalApi
    @Provides
    fun provideSeasoning() = Seasoning()
    @Provides
    fun provideTaco(@InternalApi seasoning: Seasoning) = Taco(seasoning)
    }a

    View full-size slide

  51. Multibindings

    View full-size slide

  52. Multibindings
    • Set - @IntoSet, @ElementsIntoSet
    • Map - @IntoMap, @MapKey, etc

    View full-size slide

  53. @Module
    class FoodModule {
    @Provides
    fun provideSeasoning() = Seasoning()
    @Provides
    fun provideTaco(seasoning: Seasoning) = Taco(seasoning)
    }a

    View full-size slide

  54. @Module
    class FoodModule {
    @Provides
    fun provideSpicy() = SpicySeasoning()
    @Provides
    fun provideElPaso() = ElPasoSeasoning()
    @Provides
    fun provideTaco(
    spicy: SpicySeasoning,
    elpaso: ElPasoSeasoning
    ) = Taco(setOf(spicy, elpaso))
    }a

    View full-size slide

  55. @Module
    abstract class FoodModule {
    @Multibinds abstract fun seasonings(): Set
    @Provides
    fun provideSpicy() = SpicySeasoning()
    @Provides
    fun provideElPaso() = ElPasoSeasoning()
    @Provides
    fun provideTaco(
    spicy: SpicySeasoning,
    elpaso: ElPasoSeasoning
    ) = Taco(setOf(spicy, elpaso))
    }a

    View full-size slide

  56. @Module
    abstract class FoodModule {
    @Multibinds abstract fun seasonings(): Set
    @Provides @IntoSet
    fun provideSpicy() = SpicySeasoning()
    @Provides @IntoSet
    fun provideElPaso() = ElPasoSeasoning()
    @Provides
    fun provideTaco(seasonings: Set) = Taco(seasonings)
    }a

    View full-size slide

  57. @Module(includes = SeasoningModule::class)
    abstract class FoodModule {
    @Multibinds abstract fun seasonings(): Set
    @Provides
    fun provideTaco(seasonings: Set) = Taco(seasonings)
    }a
    @Module
    class SeasoningModule {
    @Provides @IntoSet
    fun provideSpicy() = SpicySeasoning()
    @Provides @IntoSet
    fun provideElPaso() = ElPasoSeasoning()
    }

    View full-size slide

  58. @Module
    abstract class FoodModule {
    @Multibinds abstract fun seasonings(): Set
    @Provides
    fun provideTaco(seasonings: Set) = Taco(seasonings)
    }a

    View full-size slide

  59. @Module
    abstract class NetworkModule {
    @Multibinds abstract fun interceptors(): Set
    @Provides
    fun provideOkHttp(interceptors: Set) = // ...
    }a

    View full-size slide

  60. @Module
    abstract class DataModule {
    @Multibinds abstract fun adapters(): Set
    @Provides
    fun provideMoshi(adapters: Set) = // ...
    }a

    View full-size slide

  61. @Module
    abstract class LoggingModule {
    @Multibinds abstract fun trees(): Set
    }a

    View full-size slide

  62. @Module
    abstract class PetsScreenModule {
    @Multibinds
    abstract fun categories(): Set
    }a

    View full-size slide

  63. @Module
    abstract class PetsScreenModule {
    @Multibinds
    abstract fun categories(): Map
    }a

    View full-size slide

  64. @Module
    abstract class PetsScreenModule {
    @Multibinds
    abstract fun categories(): Map>
    }a

    View full-size slide

  65. @Module
    abstract class PetsScreenModule {
    @Multibinds
    abstract fun categories(): Map>
    }a

    View full-size slide

  66. data class ScreenKey(
    val id: String
    )a

    View full-size slide

  67. data class ScreenKey(
    val id: String,
    val xpId: String
    )a

    View full-size slide

  68. data class ScreenKey(
    val id: String,
    val xpId: String
    )a
    class ScreenPresenter @Inject constructor(
    screens: Map>,
    private val xpManager: XpManager
    ) {
    private val finalScreens = screens.filterKeys {
    xpManager.isEnabled(it.xpId)
    }
    }

    View full-size slide

  69. Dagger Party Tricks
    • github.com/ZacSweers/CatchUp
    • github.com/JakeWharton/sdksearch
    • github.com/JakeWharton/u2020
    • Dagger Powered Plugin System post: https://
    bit.ly/2T2Yqkl
    • Dependency Injection in a multi module
    project: https://bit.ly/2Gz5YFR
    @ZacSweers

    View full-size slide