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

Road to Modularization(DroidCon NYC 2019)

Aaron He
August 27, 2019

Road to Modularization(DroidCon NYC 2019)

First commit of Tinder Android app was in April 2013, now the app has grown to over 1.5M LOC, with over 100M downloads on Play store. Android development has changed significantly during the time. Kotlin release versions used to have M-notation, now we live in a Kotlin first world, and everyone is waiting on kapt.

The benefits of modularization are well known in the community. But what are the steps to get there? In this talk, we will briefly cover our app architecture, and the motivation behind the initial effort in 2016, and how we got to an app with over 300 modules. Then we will dive into several modularization strategies where we started getting immediate benefit of a drastically reduced feedback loop.

But the road paved with good intentions might lead you to hell. Through many many bumps, crashes and accidents on the road, those strategies have been evolved to answer many open questions we weren’t sure about initially. How can Dagger help with separation of concerns? How granular each module should be? Where is the glue to stitch everything together? And finally, has build speed actually improved? Join Aaron and Siggi from Android Platform team at Tinder to learn a few things about modularization!

Aaron He

August 27, 2019
Tweet

More Decks by Aaron He

Other Decks in Technology

Transcript

  1. • 31 Android Engineers + 23 openings • Hundreds commits

    every week • 2M LOC(85% Kotlin) Android Engineering
  2. • 31 Android Engineers + 23 openings • Hundreds commits

    every week • 2M LOC(85% Kotlin) • 300+ modules Android Engineering
  3. • 31 Android Engineers + 23 openings • Hundreds commits

    every week • 2M LOC(85% Kotlin) • 300+ modules • 1 hour clean build Android Engineering
  4. Android Engineering • 31 Android Engineers + 23 openings •

    Hundreds commits every week • 2M LOC(85% Kotlin) • 300+ modules • 6 min clean build
  5. Feature A Feature B Feature A Resources Deps Domain Common

    Base Common Deps Tinder A <I> “Contracts”
  6. Feature A Feature B Feature A Resources Deps Domain Common

    Base Common Deps Tinder A <I> “Contracts” A Contracts <implements>
  7. Feature A Feature B Feature A Resources Deps Common Base

    Common Deps Tinder A <I> “Contracts” A Contracts
  8. Feature A Feature B Feature A Resources Deps Common Base

    Common Deps Tinder A <I> “Contracts” A Contracts Feature B B B Contracts <I> “Contracts”
  9. Feature A Feature B Feature A Resources Deps Common Base

    Common Deps Tinder A <I> “Contracts” B Contracts Feature B B <I> “Contracts” A Contracts Feature A
  10. app-lifecycle app-process androidx animation blur navigation resources view dialogs rx-recyclerview

    reactivex-view runtime-permissions collections datetime glide-transformations locale database logger location moshi kotlinx-coroutines
  11. Feature A Feature B Feature A Resources Deps Common Base

    Common Deps Tinder A Com <I> “Contracts” B Contracts Feature B B <I> “Contracts” A Contracts Feature A common:resources common:reactivex common:logger common:app-lifecycle
  12. Feature A Feature B Feature A Resources Deps Tinder A

    <I> “Contracts” B Contracts Feature B B <I> “Contracts” A Contracts Feature A common:resources common:reactivex common:logger common:app-lifecycle
  13. :ui Resources Deps Tinder B Contracts Feature B :domain A

    Contracts Feature A :data :ui :domain :data common:resources common:reactivex common:logger common:app-lifecycle
  14. flavorDimensions 'feature' productFlavors { recs { dimension 'feature' } account

    { dimension 'feature' } intropricing { dimension 'feature' } profilesuggestions { dimension 'feature' }
  15. flavorDimensions 'feature' productFlavors { recs { dimension 'feature' } account

    { dimension 'feature' } intropricing { dimension 'feature' } profilesuggestions { dimension 'feature' } profileshare { dimension 'feature' } toppicks { dimension 'feature' } // ... }
  16. dimension 'feature' } profileshare { dimension 'feature' } toppicks {

    dimension 'feature' } // ... } recsImplementation project(':recs:ui') // demo build.gradle
  17. appComponentProvider.get() .plus(new OnboardingModule()) .inject(this); @ActivityScope @Subcomponent( modules = [ OnboardingModule::class

    ] ) interface OnboardingComponent { fun inject(nameStepView: NameStepView) } public interface AppComponent { OnboardingComponent plus(OnboardingModule module); }
  18. appComponentProvider.get() .plus(new OnboardingModule()) .inject(this); @ActivityScope @Subcomponent( modules = [ OnboardingModule::class

    ] ) interface OnboardingComponent { fun inject(nameStepView: NameStepView) } public interface AppComponent { OnboardingComponent plus(OnboardingModule module); }
  19. @Component( dependencies = [FeatureComponent.Parent::class], modules = [FeatureModule::class] ) interface FeatureComponent

    { fun inject(activity: FeatureActivity) interface Parent { fun repository(): Repository fun logger(): Logger fun analyticsTracker(): AnalyticsTracker } } interface FeatureComponentProvider { fun provideFeatureComponent(): FeatureComponent }
  20. modules = [FeatureModule::class] ) interface FeatureComponent { fun inject(activity: FeatureActivity)

    interface Parent { fun repository(): Repository fun logger(): Logger fun analyticsTracker(): AnalyticsTracker } } interface FeatureComponentProvider { fun provideFeatureComponent(): FeatureComponent } @Singleton @Component(...) interface AppComponent : FeatureComponent.Parent
  21. } interface FeatureComponentProvider { fun provideFeatureComponent(): FeatureComponent } @Singleton @Component(...)

    interface AppComponent : FeatureComponent.Parent val component = (context as FeatureComponentProvider) .provideFeatureComponent() // Application val component: FeatureComponent = DaggerFeatureComponent.builder() .parent(appComponent) .build()
  22. interface FeatureComponentProvider { fun provideFeatureComponent(): FeatureComponent } @Singleton @Component(...) interface

    AppComponent : FeatureComponent.Parent val component = (context as FeatureComponentProvider) .provideFeatureComponent() // Application val component: FeatureComponent = DaggerFeatureComponent.builder() .parent(appComponent) .build()
  23. @Singleton @Component(...) interface AppComponent : FeatureComponent.Parent // Feature module val

    component = (context as FeatureComponentProvider) .provideFeatureComponent() // Application val component: FeatureComponent = DaggerFeatureComponent.builder() .parent(appComponent) .build()