$30 off During Our Annual Pro Sale. View Details »

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

  2. Dagger?

  3. Dagger Party Tricks

  4. 4ish Dagger Party Tricks

  5. Deferred Initialization

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

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

    fun provideRetrofit(client: OkHttpClient): Retrofit { return Retrofit.Builder() .client(client) .build() }a
  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
  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
  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
  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
  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
  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
  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
  18. Lazy!

  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
  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<Retrofit> )e 100-150ms!
  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<Retrofit> )e 100-150ms! retrofit.get().create(...)
  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<Retrofit> )e 100-150ms! retrofit.get().create(...)
  23. @Provides fun provideRetrofit(client: OkHttpClient): Retrofit { return Retrofit.Builder() .client(client) .build()

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

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

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

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

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

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

    client.get().newCall(it) } .build() }a
  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<Retrofit> )e 100-150ms! retrofit.get().create(...)
  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<Retrofit> )e 100-150ms! retrofit.get().create(...) Main thread!
  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<OkHttpClient>): Retrofit { return Retrofit.Builder() .callFactory { client.get().newCall(it) } .build() }a @Inject constructor( retrofit: Retrofit )e 100-150ms! Background thread
  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<OkHttpClient>): Retrofit { return Retrofit.Builder() .callFactory { client.get().newCall(it) } .build() }a @Inject constructor( retrofit: Retrofit )e 100-150ms! Background thread
  34. Modularizing

  35. @Module class MyGiantModule { @Provides fun provideSeasoning() = Seasoning() @Provides

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

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

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

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

    @Module(includes = SeasoningModule::class) class MyGiantModule { @Provides fun provideTaco(seasoning: Seasoning) = Taco(seasoning) }a
  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
  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
  42. @Module class SeasoningModule { @Provides fun provideSeasoning() = Seasoning() }b

    @Module(includes = SeasoningModule::class) class TacoModule { @Provides fun provideTaco(seasoning: Seasoning) = Taco(seasoning) }c
  43. class Seasoning @Inject constructor() class Taco @Inject constructor(seasoning: Seasoning)

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

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

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

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

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

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

    Taco_Factory Feature B App
  50. Internal APIs

  51. @Module class FoodModule { @Provides fun provideSeasoning() = Seasoning() @Provides

    fun provideTaco(seasoning: Seasoning) = Taco(seasoning) }
  52. @Qualifier annotation class InternalApi

  53. @Qualifier private annotation class InternalApi

  54. @Qualifier private annotation class InternalApi @Module class FoodModule { @Provides

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

    @Provides fun provideSeasoning() = Seasoning() @Provides fun provideTaco(@InternalApi seasoning: Seasoning) = Taco(seasoning) }a
  56. Multibindings

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

    @MapKey, etc
  58. @Module class FoodModule { @Provides fun provideSeasoning() = Seasoning() @Provides

    fun provideTaco(seasoning: Seasoning) = Taco(seasoning) }a
  59. @Module class FoodModule { @Provides fun provideSpicy() = SpicySeasoning() @Provides

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

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

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

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

    @Provides fun provideTaco(seasonings: Set<Seasoning>) = Taco(seasonings) }a
  64. @Module abstract class NetworkModule { @Multibinds abstract fun interceptors(): Set<Interceptor>

    @Provides fun provideOkHttp(interceptors: Set<Interceptors>) = // ... }a
  65. @Module abstract class DataModule { @Multibinds abstract fun adapters(): Set<JsonAdapter>

    @Provides fun provideMoshi(adapters: Set<JsonAdapter>) = // ... }a
  66. @Module abstract class LoggingModule { @Multibinds abstract fun trees(): Set<Timber.Tree>

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

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

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

    Lazy<Category>> }a
  70. @Module abstract class PetsScreenModule { @Multibinds abstract fun categories(): Map<CategoryKey,

    Provider<Category>> }a
  71. None
  72. data class ScreenKey( val id: String )a

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

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

    class ScreenPresenter @Inject constructor( screens: Map<ScreenKey, Provider<Screen>>, private val xpManager: XpManager ) { private val finalScreens = screens.filterKeys { xpManager.isEnabled(it.xpId) } }
  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