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

Dagger 2 Best Practices

Dagger 2 Best Practices

Create a perfect Dagger setup with your codebase can become a challenge even with those who are already familiar with Dagger 2. This talk covers some of the problems you would stumble upon when setting up Dagger in a real-world application, give you best practices on the journey with Dagger.

Avatar for nlgtuankiet

nlgtuankiet

May 23, 2019

More Decks by nlgtuankiet

Other Decks in Programming

Transcript

  1. Outline • What’s new • Extend the graph • Dagger

    in multi-module project • Constructor injection
  2. @Component( dependencies = [FooComponent!::class], modules = [BarModule!::class] ) interface ActivityComponent

    { @Component.Builder interface Builder { @BindsInstance fun activity(activity: Activity): Builder fun foo(fooComponent: FooComponent): Builder fun bar(barModule: BarModule): Builder fun create(): ActivityComponent } } Component Factory
  3. @Component( dependencies = [FooComponent!::class], modules = [BarModule!::class] ) interface ActivityComponent

    { @Component.Factory interface Factory { fun create( @BindsInstance activity: Activity, fooComponent: FooComponent, barModule: BarModule ): ActivityComponent } } Component Factory
  4. The graph Aka dependency graph Database Api DataRepository ApplicationComponent Context

    @Component( modules = [FooActivityModule!::class] ) interface ApplicationComponent @Module( subcomponents = [FooActivityComponent!::class] ) interface FooActivityModule @Subcomponent interface FooActivityComponent { @Subcomponent.Factory interface Factory { fun create( @BindsInstance activity: FooActivity ) } }
  5. The graph Aka dependency graph Database Api DataRepository ApplicationComponent Context

    FooActivityComponent FooActivity @Component( modules = [FooActivityModule!::class] ) interface ApplicationComponent @Module( subcomponents = [FooActivityComponent!::class] ) interface FooActivityModule @Subcomponent interface FooActivityComponent { @Subcomponent.Factory interface Factory { fun create( @BindsInstance activity: FooActivity ) } }
  6. The graph Aka dependency graph Database Api DataRepository ApplicationComponent Context

    FooActivityComponent FooActivity FooFragmentComponent FooFragment @Component( modules = [FooActivityModule!::class] ) interface ApplicationComponent @Module( subcomponents = [FooActivityComponent!::class] ) interface FooActivityModule @Subcomponent interface FooActivityComponent { @Subcomponent.Factory interface Factory { fun create( @BindsInstance activity: FooActivity ) } }
  7. @Subcomponent.Factory interface Factory { fun create( @BindsInstance activity: FooActivity )

    } } Dependency Request @Module object Provision { @Provides fun viewModel(activity: FooActivity): FooViewModel = activity.viewModel @Provides fun args(activity: FooActivity): FooActivityArgs { return FooActivityArgs.fromBundle(activity.intent.extras) } @Provides fun navigator(activity: FooActivity): FooNavigator = FooNavigator(activity) } @Subcomponent(modules = [Provision!::class]) interface FooActivityComponent {
  8. @Subcomponent.Factory interface Factory { fun create( @BindsInstance activity: FooActivity )

    } } Dependency Request @Module object Provision { @Provides fun viewModel(activity: FooActivity): FooViewModel = activity.viewModel @Provides fun args(activity: FooActivity): FooActivityArgs { return FooActivityArgs.fromBundle(activity.intent.extras) } @Provides fun navigator(activity: FooActivity): FooNavigator = FooNavigator(activity) } Extended dependency @Subcomponent(modules = [Provision!::class]) interface FooActivityComponent {
  9. @Subcomponent.Factory interface Factory { fun create( @BindsInstance activity: FooActivity )

    } } Dependency Request @Module object Provision { @Provides fun viewModel(activity: FooActivity): FooViewModel = activity.viewModel @Provides fun args(activity: FooActivity): FooActivityArgs { return FooActivityArgs.fromBundle(activity.intent.extras) } @Provides fun navigator(activity: FooActivity): FooNavigator = FooNavigator(activity) } Extended dependency Request Request Request @Subcomponent(modules = [Provision!::class]) interface FooActivityComponent {
  10. One time dependency • Variable that only available at runtime

    • User input • Parameters • Use only 1 time at 1 place
  11. One time dependency class DownloadUserInfoWorker @Inject constructor( private val userId:

    String, private val isSaveToDatabase: Boolean, private val okhttp: OkHttpClient, private val database: Database ) { fun doWork() { !// bla !// bla if (isSaveToDatabase) { !// bla bla } } }
  12. One time dependency class DownloadUserInfoWorker @Inject constructor( private val userId:

    String, private val isSaveToDatabase: Boolean, private val okhttp: OkHttpClient, private val database: Database ) { fun doWork() { !// bla !// bla if (isSaveToDatabase) { !// bla bla } } } Already available 
 in the graph }
  13. One time dependency class DownloadUserInfoWorker @Inject constructor( private val userId:

    String, private val isSaveToDatabase: Boolean, private val okhttp: OkHttpClient, private val database: Database ) { fun doWork() { !// bla !// bla if (isSaveToDatabase) { !// bla bla } } } Not available } Already available 
 in the graph }
  14. One time dependency class DownloadUserInfoWorker @Inject constructor( private val userId:

    String, private val isSaveToDatabase: Boolean, private val okhttp: OkHttpClient, private val database: Database ) { fun doWork() { !// bla !// bla if (isSaveToDatabase) { !// bla bla } } } Not available } Already available 
 in the graph }
  15. Setup @Subcomponent interface DownloadSubcomponent { fun worker(): DownloadUserInfoWorker @Subcomponent.Factory interface

    Factory { fun create( @BindsInstance userId: String, @BindsInstance isSaveToDatabase: Boolean ): DownloadSubcomponent } }
  16. Setup @Subcomponent interface DownloadSubcomponent { fun worker(): DownloadUserInfoWorker @Subcomponent.Factory interface

    Factory { fun create( @BindsInstance userId: String, @BindsInstance isSaveToDatabase: Boolean ): DownloadSubcomponent } } @Module( subcomponents = [ DownloadSubcomponent!::class ] ) interface WorkerModule
  17. Setup @Subcomponent interface DownloadSubcomponent { fun worker(): DownloadUserInfoWorker @Subcomponent.Factory interface

    Factory { fun create( @BindsInstance userId: String, @BindsInstance isSaveToDatabase: Boolean ): DownloadSubcomponent } } @Module( subcomponents = [ DownloadSubcomponent!::class ] ) interface WorkerModule @Component( modules = [ WorkerModule!::class ] ) interface ParentComponent
  18. Setup @Subcomponent interface DownloadSubcomponent { fun worker(): DownloadUserInfoWorker @Subcomponent.Factory interface

    Factory { fun create( @BindsInstance userId: String, @BindsInstance isSaveToDatabase: Boolean ): DownloadSubcomponent } } class Main { fun main() { val isSaveToDatabase: Boolean val userId: String val component = DaggerDownloadSubcomponent .factory() .create(isSaveToDatabase, userId) val worker = component.worker() !// finally! worker.doWork() } } @Module( subcomponents = [ DownloadSubcomponent!::class ] ) interface WorkerModule @Component( modules = [ WorkerModule!::class ] ) interface ParentComponent
  19. The right tool for the right job • Subcomponent advantage:

    • Can request the extended dependencies at
 multiple places
  20. The right tool for the right job • Subcomponent advantage:

    • Can request the extended dependencies at
 multiple places one place
  21. One time dependency class DownloadUserInfoWorker private val userId: String, private

    val isSaveToDatabase: Boolean, private val okhttp: OkHttpClient, private val database: Database ) { fun doWork() { !// bla bla !// bla bla if (isSaveToDatabase) { !// bla bla } } @Inject } constructor(
  22. One time dependency class DownloadUserInfoWorker private val userId: String, private

    val isSaveToDatabase: Boolean, private val okhttp: OkHttpClient, private val database: Database ) { fun doWork() { !// bla bla !// bla bla if (isSaveToDatabase) { !// bla bla } } class Factory constructor( private val okhttp: OkHttpClient, private val database: Database ) { fun create(params: Params, userId: String): DownloadUserInfoWorker { return DownloadUserInfoWorker(params, userId, okhttp, database) } } @Inject } constructor (
  23. One time dependency fun main() { val isSaveToDatabase: Boolean val

    params: Params worker.doWork() } } val component = DaggerDownloadSubcomponent .factory() .create(isSaveToDatabase, params) component.worker() val worker = class Main {
  24. One time dependency fun main() { val isSaveToDatabase: Boolean val

    params: Params worker.doWork() } } val component = DaggerDownloadSubcomponent .factory() .create(isSaveToDatabase, params) component.worker() val worker = class Main {
  25. @Inject lateinit var factory: DownloadSubcomponent.Factory One time dependency fun main()

    { val isSaveToDatabase: Boolean val params: Params worker.doWork() } } val component = DaggerDownloadSubcomponent .factory() .create(isSaveToDatabase, params) component.worker() val worker = class Main { factory.create(params, isSave)
  26. OkHttpClient,
 Database class Factory @Inject constructor( private val okhttp: private

    val database: ) { fun create( params: Params, save: Boolean ): DownloadUserInfoWorker { return DownloadUserInfoWorker( params, save, ) } } Performance matter okhttp, database
  27. Provider<OkHttpClient>,
 Provider<Database> OkHttpClient,
 Database class Factory @Inject constructor( private val

    okhttp: private val database: ) { fun create( params: Params, save: Boolean ): DownloadUserInfoWorker { return DownloadUserInfoWorker( params, save, ) } } Performance matter okhttp, database okhttp.get(), database.get()
  28. In a nutshell • When the extended dependencies are request

    • Multiple time => Subcomponent • One time => Factory
  29. assisted-inject class DownloadUserInfoWorker @AssistedInject constructor( @Assisted private val params: Params,

    @Assisted private val isSaveToDatabase: Boolean, private val okhttp: OkHttpClient, private val database: Database ) { @AssistedInject interface Factory { fun create(params: Params, isSaveToDatabase: Boolean) } } // generate DownloadUserInfoWorker_AssistedFactory // https://github.com/square/AssistedInject
  30. auto-factory @AutoFactory class DownloadUserInfoWorker( private val params: Params, private val

    isSaveToDatabase: Boolean, @Provided private val okhttp: OkHttpClient, @Provided private val database: Database ) // generate DownloadUserInfoWorkerFactory // https://github.com/google/auto/tree/master/factory
  31. Dynamic Feature Module @Component(dependencies = [AppComponent::class]) interface FeatureComponent { }

    class FeatureActivity : AppCompatActivity() { } } @Component interface AppComponent {
  32. Dynamic Feature Module @Component(dependencies = [AppComponent::class]) interface FeatureComponent { fun

    inject(activity: FeatureActivity) @Component.Factory interface Factory { fun create( @BindsInstance activity: FeatureActivity, appComponent: AppComponent ): FeatureComponent } } class FeatureActivity : AppCompatActivity() { } } @Component interface AppComponent {
  33. Dynamic Feature Module @Component(dependencies = [AppComponent::class]) interface FeatureComponent { fun

    inject(activity: FeatureActivity) @Component.Factory interface Factory { fun create( @BindsInstance activity: FeatureActivity, appComponent: AppComponent ): FeatureComponent } } class FeatureActivity : AppCompatActivity() { @Inject lateinit var sharePreference: SharePreference } } @Component interface AppComponent {
  34. Dynamic Feature Module @Component(dependencies = [AppComponent::class]) interface FeatureComponent { fun

    inject(activity: FeatureActivity) @Component.Factory interface Factory { fun create( @BindsInstance activity: FeatureActivity, appComponent: AppComponent ): FeatureComponent } } class FeatureActivity : AppCompatActivity() { @Inject lateinit var sharePreference: SharePreference } fun sharePreference(): SharePreference } @Component interface AppComponent {
  35. Dynamic Feature Module @Component(dependencies = [AppComponent::class]) interface FeatureComponent { fun

    inject(activity: FeatureActivity) @Component.Factory interface Factory { fun create( @BindsInstance activity: FeatureActivity, appComponent: AppComponent ): FeatureComponent } } class FeatureActivity : AppCompatActivity() { @Inject lateinit var sharePreference: SharePreference @Inject lateinit var repository: Repository } fun sharePreference(): SharePreference } @Component interface AppComponent {
  36. Dynamic Feature Module @Component(dependencies = [AppComponent::class]) interface FeatureComponent { fun

    inject(activity: FeatureActivity) @Component.Factory interface Factory { fun create( @BindsInstance activity: FeatureActivity, appComponent: AppComponent ): FeatureComponent } } class FeatureActivity : AppCompatActivity() { @Inject lateinit var sharePreference: SharePreference @Inject lateinit var repository: Repository } fun sharePreference(): SharePreference fun repository(): Repository } @Component interface AppComponent {
  37. @Component( dependencies = [ ] ) interface FeatureComponent { @Component.Factory

    interface Factory { fun create( @BindsInstance activity: FeatureActivity, ): FeatureComponent } }
  38. @Component( dependencies = [ ] ) interface FeatureComponent { @Component.Factory

    interface Factory { fun create( @BindsInstance activity: FeatureActivity, @Component interface AndroidComponent { fun context(): Context fun sharePreference(): SharePreference } ): FeatureComponent } }
  39. @Component( dependencies = [ ] ) interface FeatureComponent { @Component.Factory

    interface Factory { fun create( @BindsInstance activity: FeatureActivity, AndroidComponent::class, @Component interface AndroidComponent { fun context(): Context fun sharePreference(): SharePreference } ): FeatureComponent } }
  40. @Component( dependencies = [ ] ) interface FeatureComponent { @Component.Factory

    interface Factory { fun create( @BindsInstance activity: FeatureActivity, AndroidComponent::class, @Component interface AndroidComponent { fun context(): Context fun sharePreference(): SharePreference } androidComponent: AndroidComponent, ): FeatureComponent } }
  41. @Component( dependencies = [ ] ) interface FeatureComponent { @Component.Factory

    interface Factory { fun create( @BindsInstance activity: FeatureActivity, AndroidComponent::class, @Component interface DataComponent { fun repository(): Repository } @Component interface AndroidComponent { fun context(): Context fun sharePreference(): SharePreference } androidComponent: AndroidComponent, ): FeatureComponent } }
  42. @Component( dependencies = [ ] ) interface FeatureComponent { @Component.Factory

    interface Factory { fun create( @BindsInstance activity: FeatureActivity, AndroidComponent::class, DataComponent::class @Component interface DataComponent { fun repository(): Repository } @Component interface AndroidComponent { fun context(): Context fun sharePreference(): SharePreference } androidComponent: AndroidComponent, ): FeatureComponent } }
  43. @Component( dependencies = [ ] ) interface FeatureComponent { @Component.Factory

    interface Factory { fun create( @BindsInstance activity: FeatureActivity, AndroidComponent::class, DataComponent::class @Component interface DataComponent { fun repository(): Repository } @Component interface AndroidComponent { fun context(): Context fun sharePreference(): SharePreference } dataComponent: DataComponent androidComponent: AndroidComponent, ): FeatureComponent } }
  44. class FeatureActivity : AppCompatActivity() { fun onCreate() { class MyApplivation

    : Application() { lateinit var androidComponent: AndroidComponent lateinit var dataComponent: DataComponent fun onCreate() { androidComponent = TODO() dataComponent = TODO() } } DaggerFeatureComponent.factory().create( dataComponent = androidComponent = activity = this ) } }
  45. class FeatureActivity : AppCompatActivity() { fun onCreate() { class MyApplivation

    : Application() { lateinit var androidComponent: AndroidComponent lateinit var dataComponent: DataComponent fun onCreate() { androidComponent = TODO() dataComponent = TODO() } } DaggerFeatureComponent.factory().create( dataComponent = androidComponent = activity = this ) } } application.dataComponent, application.androidComponent, val application: MyApplivation = applicationContext as MyApplivation
  46. @Component interface FeatureComponent { !// bla bla companion object {

    fun inject(activity: FeatureActivity) { val application = activity.applicationContext as MyApplication DaggerFeatureComponent .factory() .create( activity = activity, androidComponent = application.androidComponent, dataComponent = application.dataComponent ) .inject(activity) } } } class FeatureActivity { fun onCreate() { FeatureComponent.inject(this) } }
  47. class Sample { @Inject lateinit var heavy: HeavyDependency @Inject lateinit

    var heavyProvider: Provider<HeavyDependency> fun onButtonClick() { heavyProvider.get() // bla bla } }
  48. class Sample { @Inject lateinit var heavy: HeavyDependency @Inject lateinit

    var heavyProvider: Provider<HeavyDependency> fun onButtonClick() { heavyProvider.get() // bla bla } } “Request a provider of a dependency if you don’t need to use it right away”
  49. “Disable format generated code if you don’t need it” //

    root build.gradle allprojects { tasks.withType(JavaCompile).all { options.compilerArgs += [ '-Adagger.formatGeneratedSource=DISABLED', '-Adagger.gradle.incremental=ENABLED', ] } afterEvaluate { extensions.findByName("kapt")?.arguments { arg("dagger.formatGeneratedSource", "DISABLED") arg("dagger.gradle.incremental", "ENABLED") } } }
  50. ibrary { nstance() @Module object SampleModule { @Provides @JvmStatic fun

    provideLibrary(): SomeLibrary { return SomeLibrary.getInstance() } } Static Provision Method 15%-20% faster
 Reference: https://developer.android.com/training/articles/perf-tips
  51. FragmentFactory class MainActivity : DaggerAppCompatActivity() { @Inject lateinit var fragmentFactory:

    FragmentFactory override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) supportFragmentManager.fragmentFactory = fragmentFactory } }
  52. FragmentFactory class TodoFragmentFactory @Inject constructor() : FragmentFactory() { override fun

    instantiate( classLoader: ClassLoader, className: String ): Fragment { !// instantiate your fragment here } }
  53. Circular dependency @FragmentScope class TaskDetailNavigator @Inject constructor( private val fragment:

    TaskDetailFragment ) class TaskDetailFragment @Inject constructor( private val navigator: TaskDetailNavigator ) : Fragment()
  54. Circular dependency class TaskDetailFragment : Fragment { @Inject lateinit var

    navigator: TasksNavigator override fun onCreate(savedInstanceState: Bundle?) { !// subcomponent creation time !// !-> got fragment instance AndroidSupportInjection.inject(this) !// use fragment instance to create navigator }
  55. Circular dependency @FragmentScope class TaskDetailNavigator @Inject constructor( private val )

    class TaskDetailFragment @Inject constructor( private val navigator: TaskDetailNavigator ) : Fragment() fragment: TaskDetailFragment
  56. holder: Holder<TaskDetailFragment> Circular dependency @FragmentScope class TaskDetailNavigator @Inject constructor( private

    val ) class TaskDetailFragment @Inject constructor( private val navigator: TaskDetailNavigator ) : Fragment() class Holder<T : Any> { private var _instance: T? = null var instance: T get() = requireNotNull(_instance) set(value) { require(_instance !== null) _instance = value } }
  57. Holder Injection @Module inteface Module { @Provides @FragmentScope fun fragment(

    holder: Holder<TasksFragment>, navigator: TasksNavigator ) = TasksFragment( navigator = navigator ).also { holder.instance = it } @Provides @FragmentScope fun navigator(holder: Holder<TasksFragment>): TasksNavigator { return TasksNavigator(holder) } @Provides @FragmentScope fun holder() = Holder<TasksFragment>() }
  58. Problems • Circular dependency • Maintain scoping • Multi-binding •

    Dynamic feature module https:!//medium.com/@nlg.tuan.kiet/d9d16c892626
 https:!//github.com/nlgtuankiet/FragmentFactory