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

Effective DI for Multi Module Project

Effective DI for Multi Module Project

Dividing the module of a Monolithic Android app to realise multi-module application is considered as an important concern in recent Android development.

This session distils all the pitfalls and best practices in building a complex app and take a look at the usefulness of multi-module application and how we implemented Dependency Injection in our consumer app at Gojek(superapp.is)

Some of the highlight points of this session are :

Reintroduction to DI
Reintroduction to Dagger-Android
DI in single module projects
DI hotness in multi-module projects
Realise DI in multi-module projects using Dagger2

Adit Lal

July 09, 2019
Tweet

More Decks by Adit Lal

Other Decks in Programming

Transcript

  1. Effective DI with Multi-Modular Architecture

  2. Adit Lal GoPay $6.3B @aditlal Gojek is a Super App

    We have 18+ products from food tech to fin-tech to hyper local delivery and massage services Ride Sharing 16.5m KM
  3. app core feature 2 feature 1 Monolith Expensive Object

  4. What could possibly go wrong?

  5. Solve DI with Multi-Modular codebase

  6. Modularise?

  7. app core Feature B Feature A Feature modules Expensive Object

  8. • Organise and decouple independent pieces of functionality • Improve

    project build times • Define clear ownership boundaries between different teams Thoughts on modularisation
  9. Feature module • Owned by single team ? Set of

    team members • Encapsulates single feature • Single entry point • Smaller is better • Cannot depend on other feature modules Thoughts on modularisation
  10. App module • No feature-specific code • No infrastructure-specific code.

    • Creates the dagger component Thoughts on modularisation
  11. Ownership? core auth common Feature n-1 Feature n+2 Feature n-2

    Feature z Feature n Feature n+1 app … … … … …
  12. Ownership? core auth common Feature n-1 Feature n+2 Feature n-2

    Feature z Feature n Feature n+1 app … … … … …
  13. Inversion of control Dependency Inversion Dependency Injection IoC Principle

  14. • Declare Components that can be reused. • High level

    modules should not depend on low level modules • Build abstractions so any implementation can be easily changed with new one. • Dependencies can be injected into components. IoC Principle
  15. Should I do Modularise to use Dagger?

  16. Don’t do it for Dagger , do it for yourself

  17. Playing Jenga

  18. :feature1 F1SubComponent F1Module :feature2 F2SubComponent F2Module :core CoreComponent CoreModule Expensive

    Object :app MainComponent MainModule
  19. :app MainComponent MainModule :feature1 F1Component F1Module :feature2 F2Component F2Module :core

    CoreComponent CoreModule Expensive Object
  20. • We should prefer to be explicit with the module

    dependencies
 • We don’t want CoreComponent to know about FeatureComponent
 • It can’t check if the graph is correct. SubComponent goes away! It needs the parent = slower compilation

  21. Core @Module class CoreModule { @Provides @Singleton fun provideExpObj(): ExpensiveObject

    = ExpensiveObject() }
  22. @Component(modules = [CoreModule::class]) @Singleton interface CoreComponent { @Component.Builder interface Builder

    { fun build(): CoreComponent } fun provideExpensiveObject(): ExpensiveObject } Core
  23. class OurMainApplication : Application() { private val coreComponent: CoreComponent by

    lazy { DaggerCoreComponent .builder() .build() } companion object { @JvmStatic fun coreComponent(context: Context) = (context.applicationContext as OurMainApplication).coreComponent } }
  24. class OurMainApplication : Application() { private val coreComponent: CoreComponent by

    lazy { DaggerCoreComponent .builder() .build() } companion object { @JvmStatic fun coreComponent(context: Context) = (context.applicationContext as OurMainApplication).coreComponent } }
  25. class OurMainApplication : Application() { private val coreComponent: CoreComponent by

    lazy { DaggerCoreComponent .builder() .build() } companion object { @JvmStatic fun coreComponent(context: Context) = (context.applicationContext as OurMainApplication).coreComponent } }
  26. fun Activity.coreComponent() = OurMainApplication.coreComponent(this) Extension function

  27. @Module class Feature1Module { @Provides fun provideString() = "test" }

    Feature 1
  28. @Component(modules = [Feature1Module::class], dependencies = [CoreComponent::class]) interface Feature1Component { @Component.Builder

    interface Builder { fun coreComponent(coreComponent: CoreComponent): Builder } fun inject(activity: OtherActivity) } Feature 1
  29. DaggerFeature1Component .builder() .coreComponent(this.coreComponent) .build() .inject(this) Feature 1

  30. @Module class Feature2Module { @Provides fun provideInt() = 1 }

    Feature 2
  31. @Component(modules = [Feature2Module::class], dependencies = [CoreComponent::class]) interface Feature2Component { @Component.Builder

    interface Builder { fun coreComponent(coreComponent: CoreComponent): Builder } fun inject(activity: MainActivity) } Feature 2
  32. When dealing with component dependencies we must follow 2 simple

    rules: • An un-scoped component cannot depend on scoped components. • A scoped component cannot depend on a component with the same scope.
  33. @Scope @Retention annotation class FeatureScope

  34. @Component(modules = [Feature1Module::class], dependencies = [CoreComponent::class]) @FeatureScope interface Feature1Component {

    @Component.Builder interface Builder { fun coreComponent(coreComponent: CoreComponent): Builder } fun inject(activity: OtherActivity) }
  35. @Component(modules = [Feature2Module::class], dependencies = [CoreComponent::class]) @FeatureScope interface Feature2Component {

    @Component.Builder interface Builder { fun coreComponent(coreComponent: CoreComponent): Builder } fun inject(activity: MainActivity) }
  36. Component.dependencies • We can choose what we expose from each

    component • more verbosity • we can get dependencies from multiple components faster compilation
  37. Plan

  38. interface CoreComponentProvider { fun provideCoreComponent(): CoreComponent }

  39. object CoreInjectHelper { fun provideCoreComponent(applicationContext: Context): CoreComponent{ return if (applicationContext

    is CoreComponentProvider) { (applicationContext as CoreComponentProvider).provideCoreComponent() } else { throw IllegalStateException( "The context passed does not implement CoreComponentProvider" ) } } }
  40. object CoreInjectHelper { fun provideCoreComponent(applicationContext: Context): CoreComponent{ return if (applicationContext

    is CoreComponentProvider) { (applicationContext as CoreComponentProvider).provideCoreComponent() } else { throw IllegalStateException( "The context passed does not implement CoreComponentProvider" ) } } }
  41. object CoreInjectHelper { fun provideCoreComponent(applicationContext: Context): CoreComponent{ return if (applicationContext

    is CoreComponentProvider) { (applicationContext as CoreComponentProvider).provideCoreComponent() } else { throw IllegalStateException( "The context passed does not implement CoreComponentProvider" ) } } }
  42. class OurMainApplication : Application(), CoreComponentProvider { private lateinit var coreComponent:

    CoreComponent override fun provideCoreComponent(): CoreComponent { if (!this::coreComponent.isInitialized) { coreComponent = DaggerCoreComponent .builder() .build() } return coreComponent } }
  43. class OurMainApplication : Application(), CoreComponentProvider { private lateinit var coreComponent:

    CoreComponent override fun provideCoreComponent(): CoreComponent { if (!this::coreComponent.isInitialized) { coreComponent = DaggerCoreComponent .builder() .build() } return coreComponent } }
  44. class OurMainApplication : Application(), CoreComponentProvider { private lateinit var coreComponent:

    CoreComponent override fun provideCoreComponent(): CoreComponent { if (!this::coreComponent.isInitialized) { coreComponent = DaggerCoreComponent .builder() .build() } return coreComponent } }
  45. class MainActivity : AppCompatActivity() { @Inject lateinit var expensiveObject: ExpensiveObject

    override fun onCreate(savedInstanceState: Bundle?) { ... DaggerFeature2Component .builder() .coreComponent(this.coreComponent()) .build() .inject(this) } }
  46. class MainActivity : AppCompatActivity() { @Inject lateinit var expensiveObject: ExpensiveObject

    override fun onCreate(savedInstanceState: Bundle?) { ... DaggerFeature2Component .builder() .coreComponent(this.coreComponent()) .build() .inject(this) } }
  47. Dagger.Android

  48. @Scope annotation class ActivityScope

  49. @Module(includes = [ AndroidSupportInjectionModule::class ]) abstract class AppModule { @ActivityScope

    @ContributesAndroidInjector() abstract fun contributesMainActivityInjector(): MainActivity @ActivityScope @ContributesAndroidInjector() abstract fun contributesOtherActivityInjector(): OtherActivity }
  50. @Module(includes = [ AndroidSupportInjectionModule::class ]) abstract class AppModule { @ActivityScope

    @ContributesAndroidInjector() abstract fun contributesMainActivityInjector(): MainActivity @ActivityScope @ContributesAndroidInjector() abstract fun contributesOtherActivityInjector(): OtherActivity }
  51. class OurMainApplication : Application(), CoreComponentProvider, HasActivityInjector { @Inject lateinit var

    dispatchingActivityInjector: DispatchingAndroidInjector<Activity> private lateinit var coreComponent: CoreComponent override fun onCreate() { super.onCreate() DaggerAppComponent.create().inject(this) } override fun activityInjector(): AndroidInjector<Activity> = dispatchingActivityInjector }
  52. class OurMainApplication : Application(), CoreComponentProvider, HasActivityInjector { @Inject lateinit var

    dispatchingActivityInjector: DispatchingAndroidInjector<Activity> private lateinit var coreComponent: CoreComponent override fun onCreate() { super.onCreate() DaggerAppComponent.create().inject(this) } override fun activityInjector(): AndroidInjector<Activity> = dispatchingActivityInjector }
  53. class OurMainApplication : Application(), CoreComponentProvider, HasActivityInjector { @Inject lateinit var

    dispatchingActivityInjector: DispatchingAndroidInjector<Activity> private lateinit var coreComponent: CoreComponent override fun onCreate() { super.onCreate() DaggerAppComponent.create().inject(this) } override fun activityInjector(): AndroidInjector<Activity> = dispatchingActivityInjector }
  54. class OurMainApplication : Application(), CoreComponentProvider, HasActivityInjector { @Inject lateinit var

    dispatchingActivityInjector: DispatchingAndroidInjector<Activity> private lateinit var coreComponent: CoreComponent override fun onCreate() { super.onCreate() DaggerAppComponent.create().inject(this) } override fun activityInjector(): AndroidInjector<Activity> = dispatchingActivityInjector }
  55. class MainActivity : AppCompatActivity() { @Inject lateinit var expensiveObject: ExpensiveObject

    override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) ... } }
  56. class MainActivity : AppCompatActivity() { @Inject lateinit var expensiveObject: ExpensiveObject

    override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) ... } }
  57. AndroidInjection.inject(this) Activity Fragment Service Content Provider Broadcast Receiver this

  58. Tip @Inject lateinit var expensiveObject: Lazy<ExpensiveObject> expensiveObject.get()

  59. • Always go for static provide methods. • Nobody cares

    about name of your Scope • Expose the application context through a component builder instead of a module with constructor argument. Or even better: use the new factory! Tip
  60. @Module class Feature1Module { @Provides fun provideString() = "test" }

    Tip
  61. @Module abstract class Feature1Module { @Binds abstract fun provideString() }

    Tip
  62. Tip Module Module Factory Component @Provides Create a inject object

    Component @Binds Create a inject object
  63. Tip @Binds @IntoMap @ViewModelKey(X1::class) abstract bindUserViewModel(x1 : ObjX) : ObjX

  64. • Provide dependencies through class constructors • Avoid unnecessary scoping

    - Use scope annotations sparingly and judiciously Tl;Dr
  65. • Use reusable components for expensive and immutable dependencies •

    Use @Binds instead of a @Provides method when simply delegating one type to another. • Write tests , switch dependencies on tests with a test component instead of overriding modules. Tl;Dr
  66. • Using Dagger in multi-module by Marcos Holgado • Dependency

    injection in a multi module project - Plaid - Ben Weiss • Modularising Android Application by Marvin Ramin Resources
  67. https://superapp.is https://www.gojek.io

  68. Adit Lal @aditlal adit.dev Thats all folks!