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

Modularizing Legacy Apps

Modularizing Legacy Apps

Most of the existing Android applications are structured as a single module and this is proven to be a good solution for small projects and teams. Unfortunately, as a project grows, the developer team expands, and the complexity of working in parallel increases, building time raises, and productivity drops. Modularisation is the technique of separating the codebase into independent modules so to isolate specific functionalities, simplify parallel development, and support Dynamic Features. In this presentation I will explain how to modularise a legacy Android application by showing challenges and achievements experienced in the reformatting of Quandoo's application.

Marcello Galhardo

March 01, 2019
Tweet

More Decks by Marcello Galhardo

Other Decks in Programming

Transcript

  1. Modularizing Legacy Apps
    A PRAGMATIC APPROACH
    Marcello Galhardo

    Slides at https://tinyurl.com/y9zcf4u9

    View full-size slide

  2. Marcello Galhardo
    Android Dev @ Quandoo

    Moderator @ Android Dev BR

    View full-size slide

  3. Why?
    • Build takes too long

    • Code ownership

    • Separation of concerns

    • Dynamic Features

    View full-size slide

  4. Strategy #1
    Quality Directives

    View full-size slide

  5. • Code must be under continuous integration

    • Automate the build

    • Test coverage measure the code quality

    • Small Feature Branches and Pull Requests

    View full-size slide

  6. • Code must be under continuous integration
    • Automate the build
    • Test coverage measure the code quality
    • Small Feature Branches and Pull Requests

    View full-size slide

  7. Write code Push code to Git Run unit and ui tests
    Build generation
    Deploy on Store
    Distribute to QAs

    View full-size slide

  8. • Code must be under continuous integration

    • Automate the build

    • Test coverage measure the code quality

    • Small Feature Branches and Pull Requests

    View full-size slide

  9. [#ticket] New search page in separated module
    ? authored 2 weeks ago
    Showing 272 changed files with 4582 additions and 365 deletions

    View full-size slide

  10. New Branch
    Your Branch
    Cherry-pick Cherry-pick
    PR 1 PR 2
    Develop Branch

    View full-size slide

  11. Strategy #2
    Scout the Codebase

    View full-size slide

  12. • Keep things you need

    • Donate things someone else might need

    • Discard or recycle the rest

    View full-size slide

  13. fun View.isVisible() = this.visibility == View.VISIBLE
    @Deprecated(
    message = "Replaced by ViewKt.",
    level = DeprecationLevel.ERROR,
    replaceWith = ReplaceWith(
    expression = "ViewKt"
    )
    )
    object ViewUtils {…}

    View full-size slide

  14. Strategy #3
    Keep It Simple, Stupid

    View full-size slide

  15. • Don’t create modules that you don’t need

    • Don’t plan too much ahead: Emergent Design

    • There are no final decisions

    • "Branch by Abstraction” when extracting modules

    View full-size slide

  16. Strategy #4
    Don’t Talk to Strangers

    View full-size slide

  17. • Modules shouldn't reveal anything unnecessary to other modules

    • Modules don’t rely on other modules' implementations

    • Explicitly pass required data into your modules

    • Avoid similar modules: duplication is a symptom of structural problems

    View full-size slide

  18. Strategy #5
    Don’t Repeat Yourself

    View full-size slide

  19. • Make it easier to share code

    • Centralise your resources

    • Share versions of dependencies using variables

    View full-size slide

  20. • Make it easier to share code
    • Centralise your resources
    • Share versions of dependencies using variables

    View full-size slide

  21. Feature 2
    Feature 1

    View full-size slide

  22. Feature 2
    Feature 1
    Common

    View full-size slide

  23. Feature 2
    Feature 1
    Common

    View full-size slide

  24. Build Output
    :feature2
    :feature1
    :base
    Use Use

    View full-size slide

  25. • Make it easier to share code

    • Centralise your resources

    • Share versions of dependencies using variables

    View full-size slide

  26. // ../scripts/gradle/base-build.gradle
    apply plugin: 'kotlin-android'
    apply plugin: 'kotlin-android-extensions'
    apply plugin: ‘kotlin-kapt'
    android {
    compileSdkVersion compile_sdk_version
    buildToolsVersion build_tools_version
    defaultConfig {
    minSdkVersion min_sdk_version
    targetSdkVersion target_sdk_version
    versionCode version_code
    versionName version_name
    }
    buildTypes {…}
    }
    dependencies {
    implementation “org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    }

    View full-size slide

  27. // Feature module
    apply plugin: 'com.android.feature'
    apply from: "../scripts/gradle/base-build.gradle"
    dependencies {…}
    // Library module
    apply plugin: 'com.android.library'
    apply from: “../scripts/gradle/base-build.gradle"
    dependencies {…}

    View full-size slide

  28. Strategy #6
    Small Interactions

    View full-size slide

  29. :legacy
    :feature
    :facade Invokes new feature
    Access legacy functionalities
    by well-defined interface and
    dependency injection
    Implements interfaces on
    legacy code

    View full-size slide

  30. :legacy
    :feature2
    :facade
    :feature1 :feature3
    Extract features
    from :legacy

    View full-size slide

  31. :app
    :feature2
    :base
    :feature1 :feature3
    Move interfaces from
    facade and provide new
    Implementation to :base

    View full-size slide

  32. IntelliJ IDEA rocks.
    Luckily, Android Studio is powered by IntelliJ IDEA.
    https://www.jetbrains.com/help/idea/refactoring-
    source-code.html

    View full-size slide

  33. Strategy #7
    Vertical Slice Architecture

    View full-size slide

  34. • Avoid “N-Tier Architectures”

    • Architectural boundaries don’t run between modules but rather through
    them

    View full-size slide

  35. • Avoid “N-Tier Architectures”
    • Architectural boundaries don’t run between modules but rather through
    them

    View full-size slide

  36. :app
    :domain
    :presentation
    :data
    All repositories exposed
    to the domain module
    All use cases exposed
    to the presentation module
    :app knows about all modules
    and up the DI framework

    View full-size slide

  37. Domain
    Presentation
    Data
    Feature

    View full-size slide

  38. • Avoid “N-Tier Architectures”

    • Architectural boundaries don’t run between modules but rather
    through them

    View full-size slide

  39. :app
    :feature2
    :base
    :feature1
    :component

    View full-size slide

  40. :module
    domain
    data
    ui
    model
    use case

    repository
    activity
    fragment
    view model
    store
    repository
    local
    remote

    View full-size slide

  41. Strategy #8
    Dependency Injection

    View full-size slide

  42. :app
    App
    Component
    Discovery
    Component
    Base
    Component
    Use
    Depend
    Build
    :discovery
    Build
    :base
    Use Use
    Build
    Use

    View full-size slide

  43. @Singleton
    @Component(modules = [
    BaseModule::class
    ])
    interface BaseComponent {
    {…}
    fun router(): DiscoveryRouter
    companion object {
    // workaround
    val INSTANCE: BaseComponent = DaggerBaseComponent.create()
    }
    }

    View full-size slide

  44. @ActivityScope
    @Component(
    modules = [
    DiscoveryModule::class
    ],
    dependencies = [
    BaseComponent::class
    ])
    interface DiscoveryComponent : AndroidInjector {
    @Component.Builder
    abstract class Builder : AndroidInjector.Builder() {
    abstract fun plus(component: BaseComponent): Builder
    }
    }

    View full-size slide

  45. DaggerDiscoveryComponent.builder()
    .plus(BaseComponent.INSTANCE)
    .create(this)
    .inject(this)

    View full-size slide

  46. @Singleton
    @Component(modules = [
    {…}
    BaseModule::class
    ])
    interface AppComponent : AndroidInjector {
    fun inject(target: Activity)
    {…}
    }

    View full-size slide

  47. Strategy #9
    Only Talk to Your Friends

    View full-size slide

  48. :app
    :discovery
    :base
    :search
    :instantapp

    View full-size slide

  49. interface Router {
    companion object {
    const val BASE_URL = “https://example.com”
    }
    }
    interface DiscoveryRouter : Router {
    fun startAvailabilityFilterActivityForResult(activity: Activity,
    requestCode: Int,
    peopleCount: Int,
    date: LocalDate,
    time: LocalTime)
    object Params {
    const val PEOPLE_COUNT = “PEOPLE_COUNT"
    const val DATE = "DATE"
    const val TIME = "TIME"
    }
    }

    View full-size slide

  50. @Reusable
    class UrlDiscoveryRouter @Inject constructor() : DiscoveryRouter {
    override fun startAvailabilityFilterActivityForResult(activity: Activity,
    requestCode: Int,
    peopleCount: Int,
    date: LocalDate,
    time: LocalTime) {
    val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)).apply {
    addCategory(Intent.CATEGORY_DEFAULT)
    addCategory(Intent.CATEGORY_BROWSABLE)
    setPackage(activity.packageName)
    putExtra(DiscoveryRouter.Params.PEOPLE_COUNT, peopleCount)
    putExtra(DiscoveryRouter.Params.DATE, date)
    putExtra(DiscoveryRouter.Params.TIME, time)
    }
    activity.startActivityForResult(fragment, intent, requestCode)
    }
    // companion object
    private const val URL = "${BASE_URL}/availability/filter"
    }

    View full-size slide



  51. android:name=“DiscoveryActivity”>




    android:scheme="https"
    android:host=“example.com”
    android:pathPattern="/availability/filter"/>




    View full-size slide

  52. class SimpleActivity : AppCompatActivity() {
    @Inject
    lateinit var discoveryRouter: DiscoveryRouter
    fun onButtonClicked(…) {
    discoveryRouter.startAvailabilityFilterActivityForResult(
    activity = this,
    requestCode = requestCode,
    peopleCount = peopleCount,
    date = date,
    time = time)
    }
    }

    View full-size slide

  53. Strategy #10
    Delivery Small and Iterate

    View full-size slide

  54. Start State A State B State C
    Minor
    change
    Minor
    change
    Minor
    change
    Quickly identify
    issues
    End
    Fix
    issues
    Rollback as
    last option
    New interaction

    View full-size slide

  55. A Pragmatic Approach
    1. Quality Directives

    2. Scout the Codebase

    3. Keep it Simple, Stupid

    4. Don’t Talk to Strangers

    5. Don’t Repeat Yourself

    6. Small Interactions

    7. Vertical Slice Architecture

    8. Dependency Injection

    9. Only Talk to Your Friends

    10.Delivery Small and Iterate

    View full-size slide

  56. Questions?
    We’re hiring! https://tinyurl.com/y4vfr9ud
    Marcello Galhardo

    Slides at https://tinyurl.com/y9zcf4u9

    View full-size slide