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

Android at Scale @Square

Ralf
November 25, 2019

Android at Scale @Square

Codebases naturally grow over time by adding new features, abstractions and migrating code to new architectures. We introduce layers to hide implementation details and separate concerns. Good modularization brings many benefits such as better reusability, shorter build times and code isolation.

Square builds payment, software and hardware systems that help businesses of any type. The Android Point of Sale repository faces challenges similar to other large codebases. This talk gives an overview of how the repository evolved over the years, the difficulties we encountered in the recent months and how we addressed them with clear structures and common patterns to keep up with the ongoing growth.

Ralf

November 25, 2019
Tweet

More Decks by Ralf

Other Decks in Programming

Transcript

  1. • Logical unit to perform work • Self-contained piece of

    the whole system • Loose coupling • High cohesion What is a module in modular design?
  2. • Code is easier portable • Code is more reliable

    • Code is easier to test • Code is easier to maintain • ... Advantages of modular design?
  3. • With multiple products? ◦ Yes!!! • With multiple modules?

    ◦ Yes!! • With a single module? ◦ Yes! • Without tests? ◦ ¯\_(ツ)_/¯ Do we need dependency inversion?
  4. interface LoginStrategy { fun login() } class PosLoginStrategy @Inject constructor(

    private val helper: Helper ) : LoginStrategy { override fun login() { } @dagger.Module abstract class Module { @Binds abstract fun bindLoginStrategy(strategy: PosLoginStrategy): LoginStrategy } } Dependency inversion
  5. class PosLoginStrategy @Inject constructor( private val helper: Helper, private val

    helper: Helper1, private val helper: Helper2, private val helper: Helper3, private val helper: Helper4, private val helper: Helper5, private val helper: Helper6, private val helper: Helper7, private val helper: Helper8 ) : LoginStrategy Dependencies
  6. dependencies { api project(':helper1') api project(':helper2') api project(':helper3') api project(':helper4')

    api project(':helper5') api project(':helper6') api project(':helper7') api project(':helper8') } Dependencies
  7. dependencies { api project(':login-strategy') api project(':helper1') api project(':helper2') api project(':helper3')

    api project(':helper4') api project(':helper5') api project(':helper6') api project(':helper7') api project(':helper8') } Dependencies for Feature
  8. Legacy modules • All the rules of the new module

    structure don’t apply ◦ (They’re all the module types in one) • This doesn’t mean you shouldn’t care about dependencies and their size!
  9. // :public interface LoginStrategy { fun login() } // :impl

    class PosLoginStrategy @Inject constructor( private val helper: Helper ) : LoginStrategy { override fun login() { } } // :impl-wiring @dagger.Module abstract class LoginStrategyModule { @Binds abstract fun bindLoginStrategy(strategy: PosLoginStrategy): LoginStrategy } Module Structure
  10. dependencies { api project(':login-strategy') api project(':helper1') api project(':helper2') api project(':helper3')

    api project(':helper4') api project(':helper5') api project(':helper6') api project(':helper7') api project(':helper8') } Module Structure
  11. Development Apps • Install a feature with an isolated application

    on the device ◦ Launch the feature directly ◦ Shorter build times • Replace dependencies by fakes • Isolated UI tests
  12. :login-screen:impl class SampleLoginScreenWorkflow @Inject constructor( private val accountScreenWorkflow: Provider<AccountScreenWorkflow> )

    : LoginScreenWorkflow, StatefulWorkflow<Unit, LoginScreenState, Unit, LayeredScreen<PosLayering>>() { override fun render( props: Unit, state: LoginScreenState, context: RenderContext<LoginScreenState, Unit> ): LayeredScreen<PosLayering> { return when (state) { is RenderAccountScreen -> { context.renderChild(accountScreenWorkflow.get(), Unit) { leaveAccountScreen } } } } }
  13. :login-screen:demo android { defaultConfig { applicationId "com.squareup.sample.login.screen.demo" } } dependencies

    { // … implementation project(':samples:account-screen-sample:account-screen:fake-wiring') }
  14. Fakes • Reduce size of the dependency graph • Reduce

    the number of lines of code that need to be compiled • Allow changing behavior as you wish
  15. Summary • Square isn’t different, has its own set of

    unique problems • Modularization is an ongoing process, celebrate as you make progress • Start thinking about dependencies and build time early in the process • Make use of design patterns like dependency inversion
  16. Q&A