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 Slide

  2. Marcello Galhardo
    Android Dev @ Quandoo

    Moderator @ Android Dev BR

    View Slide

  3. Why?
    • Build takes too long

    • Code ownership

    • Separation of concerns

    • Dynamic Features

    View Slide

  4. Strategy #1
    Quality Directives

    View Slide

  5. • Code must be under continuous integration

    • Automate the build

    • Test coverage measure the code quality

    • Small Feature Branches and Pull Requests

    View Slide

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

    View Slide

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

    View Slide

  8. • Code must be under continuous integration

    • Automate the build

    • Test coverage measure the code quality

    • Small Feature Branches and Pull Requests

    View Slide

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

    View Slide

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

    View Slide

  11. Strategy #2
    Scout the Codebase

    View Slide

  12. • Keep things you need

    • Donate things someone else might need

    • Discard or recycle the rest

    View Slide

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

    View Slide

  14. Strategy #3
    Keep It Simple, Stupid

    View 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 Slide

  16. Strategy #4
    Don’t Talk to Strangers

    View 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 Slide

  18. Strategy #5
    Don’t Repeat Yourself

    View Slide

  19. • Make it easier to share code

    • Centralise your resources

    • Share versions of dependencies using variables

    View Slide

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

    View Slide

  21. App

    View Slide

  22. Feature 2
    Feature 1

    View Slide

  23. Feature 2
    Feature 1
    Common

    View Slide

  24. Feature 2
    Feature 1
    Common

    View Slide

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

    View Slide

  26. • Make it easier to share code

    • Centralise your resources

    • Share versions of dependencies using variables

    View Slide

  27. // ../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 Slide

  28. // 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 Slide

  29. Strategy #6
    Small Interactions

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  34. Strategy #7
    Vertical Slice Architecture

    View Slide

  35. • Avoid “N-Tier Architectures”

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

    View Slide

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

    View Slide

  37. :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 Slide

  38. Domain
    Presentation
    Data
    Feature

    View Slide

  39. • Avoid “N-Tier Architectures”

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

    View Slide

  40. :app
    :feature2
    :base
    :feature1
    :component

    View Slide

  41. :module
    domain
    data
    ui
    model
    use case

    repository
    activity
    fragment
    view model
    store
    repository
    local
    remote

    View Slide

  42. Strategy #8
    Dependency Injection

    View Slide

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

    View Slide

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

    View Slide

  45. @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 Slide

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

    View Slide

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

    View Slide

  48. Strategy #9
    Only Talk to Your Friends

    View Slide

  49. :app
    :discovery
    :base
    :search
    :instantapp

    View Slide

  50. 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 Slide

  51. @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 Slide



  52. android:name=“DiscoveryActivity”>




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




    View Slide

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

    View Slide

  54. Strategy #10
    Delivery Small and Iterate

    View Slide

  55. 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 Slide

  56. 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 Slide

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

    Slides at https://tinyurl.com/y9zcf4u9

    View Slide