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 Slide

  2. Dagger?

    View Slide

  3. Dagger Party Tricks

    View Slide

  4. 4ish Dagger Party Tricks

    View Slide

  5. Deferred
    Initialization

    View Slide

  6. View Slide

  7. View Slide

  8. View Slide

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

    View Slide

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

    View Slide

  11. @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 Slide

  12. @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 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
    @Inject
    constructor(
    retrofit: Retrofit
    )e

    View 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
    @Inject
    constructor(
    retrofit: Retrofit
    )e

    View 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: Retrofit
    )e

    View 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: Retrofit
    )e

    View 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
    100-150ms!
    @Inject
    constructor(
    retrofit: Retrofit
    )e

    View Slide

  18. Lazy!

    View Slide

  19. 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 Slide

  20. 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 Slide

  21. 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 Slide

  22. 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 Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  30. 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 Slide

  31. 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 Slide

  32. 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 Slide

  33. 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 Slide

  34. Modularizing

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  40. @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 Slide

  41. @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 Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  50. Internal APIs

    View Slide

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

    View Slide

  52. @Qualifier
    annotation class InternalApi

    View Slide

  53. @Qualifier
    private annotation class InternalApi

    View Slide

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

    View Slide

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

    View Slide

  56. Multibindings

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  60. @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 Slide

  61. @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 Slide

  62. @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 Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  71. View Slide

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

    View Slide

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

    View Slide

  74. 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 Slide

  75. 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 Slide