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

7+1 tips about [Android] App Modularization | droidcon Americas

7+1 tips about [Android] App Modularization | droidcon Americas

Nowadays, App modularization is a technique largely adopted that gives lots of advantages. But how to approach it? This talk will not focus on the pro and cons of modularization but instead on the approach and the process to achieve it. In particular, I want to share the approach that I followed to modularize an existing application and a new one. I want also to share the failures and all the struggles that came in my head while approaching the process.

9da5d5cc4b6a9f28058152e28364b02a?s=128

Marco Gomiero

November 17, 2020
Tweet

Transcript

  1. Marco Gomiero droidcon Americas 7+1 tips about [Android] App Modularization

  2. droidcon Americas - @marcoGomier • 101 about modularization • Pro

    and cons ! What this talk is not about
  3. droidcon Americas - @marcoGomier • 101 about modularization • Pro

    and cons " What this talk is about • Approach and process • Failure and struggles
  4. droidcon Americas - @marcoGomier Marco Gomiero # Tech Lead @

    Uniwhere $ % & Co-Lead @ GDG Venezia > Twitter: @marcoGomier > Github: prof18 > Website: marcogomiero.com
  5. droidcon Americas - @marcoGomier We’re redesigning how college email works

    Uniwhere • ' Started in 2015 as MVP • ( Lots of iterations • ) Time for [more] solid architecture
  6. droidcon Americas - @marcoGomier Modularization in few words

  7. droidcon Americas - @marcoGomier Modularization in few words * Simple

    development +# Split responsibilities , Reusable modules Source: https://speakerdeck.com/beraldofilippo/the-clean-cut?slide=10
  8. droidcon Americas - @marcoGomier Modularization in few words - Dynamic

    features modules . Faster Builds / Simple test automation Source: https://speakerdeck.com/beraldofilippo/the-clean-cut?slide=10
  9. droidcon Americas @marcoGomier #1 Read and study how to do

  10. droidcon Americas - @marcoGomier Doing some research is always a

    good start
  11. droidcon Americas - @marcoGomier 1. Modularization - Why you should

    care 2. Modularization - A successful architecture 3. Modularization - Real-life example 4. Modularization - How to approach 5. Modularization - Lessons learned Jeroen Mols's series about modularization https://jeroenmols.com/blog/
  12. droidcon Americas - @marcoGomier Talk @ Google I/O '19

  13. droidcon Americas - @marcoGomier Talk @ Google I/O '19 https:"#www.youtube.com/watch?v=PZBg5DIzNww

  14. droidcon Americas - @marcoGomier Plaid

  15. droidcon Americas - @marcoGomier Plaid https:"#github.com/android/plaid

  16. droidcon Americas @marcoGomier #2 Sketch your modules

  17. droidcon Americas - @marcoGomier Sketching "un-confuse" your ideas

  18. droidcon Americas - @marcoGomier Monolithic Application └── app └── src

    └── main └── java └── com.prof18.myapplication ├── DashboardFragment.kt ├── HomeFragment.kt ├── MainActivity.kt ├── NotificationsFragment.kt ├── ProfileActivity.kt └── ProfileSettingsActivity.kt
  19. droidcon Americas - @marcoGomier Monolithic Application └── app └── src

    └── main └── java └── com.prof18.myapplication ├── DashboardFragment.kt ├── HomeFragment.kt ├── MainActivity.kt ├── NotificationsFragment.kt ├── ProfileActivity.kt └── ProfileSettingsActivity.kt
  20. droidcon Americas - @marcoGomier Monolithic Application └── app └── src

    └── main └── java └── com.prof18.myapplication ├── DashboardFragment.kt ├── HomeFragment.kt ├── MainActivity.kt ├── NotificationsFragment.kt ├── ProfileActivity.kt └── ProfileSettingsActivity.kt
  21. droidcon Americas - @marcoGomier

  22. droidcon Americas @marcoGomier #3 Decide your architecture

  23. droidcon Americas - @marcoGomier There is not a "right way"

  24. droidcon Americas - @marcoGomier Core Library App Feature Feature Feature

    Feature Library Modularized App Architecture
  25. droidcon Americas - @marcoGomier Core Library App Feature Feature Feature

    Feature Library Library Modules • Android or Pure Kotlin library • Never depend on feature or app • A library can depend on another
  26. droidcon Americas - @marcoGomier Core Library App Feature Feature Feature

    Feature Library Feature Modules • Android library • Never depend on other features or app • Depends on one or more libraries
  27. droidcon Americas - @marcoGomier Core Library App Feature Feature Feature

    Feature Library App Module • Android application • Link all the modules together • Depends on other features and library
  28. droidcon Americas - @marcoGomier Monolithic Application └── app └── src

    └── main └── java └── com.prof18.myapplication ├── DashboardFragment.kt ├── HomeFragment.kt ├── MainActivity.kt ├── NotificationsFragment.kt ├── ProfileActivity.kt └── ProfileSettingsActivity.kt
  29. droidcon Americas - @marcoGomier Modularized App └── app └── src

    └── main └── java └── com.prof18.myapplication ├── MainActivity.kt
  30. droidcon Americas - @marcoGomier Modularized App └── features ├── home

    │ └── src │ └── main │ └── java │ └── com.prof18.myapplication.features.home │ ├── DashboardFragment.kt │ ├── HomeActivity.kt │ ├── HomeFragment.kt │ └── NotificationsFragment.kt └── profile └── src └── main └── java └── com.prof18.myapplication.features.profile ├── ProfileActivity.kt └── ProfileSettingsActivity.kt
  31. droidcon Americas - @marcoGomier Modularized App └── features ├── home

    │ └── src │ └── main │ └── java │ └── com.prof18.myapplication.features.home │ ├── DashboardFragment.kt │ ├── HomeActivity.kt │ ├── HomeFragment.kt │ └── NotificationsFragment.kt └── profile └── src └── main └── java └── com.prof18.myapplication.features.profile ├── ProfileActivity.kt └── ProfileSettingsActivity.kt
  32. droidcon Americas - @marcoGomier Modularized App └── features ├── home

    │ └── src │ └── main │ └── java │ └── com.prof18.myapplication.features.home │ ├── DashboardFragment.kt │ ├── HomeActivity.kt │ ├── HomeFragment.kt │ └── NotificationsFragment.kt └── profile └── src └── main └── java └── com.prof18.myapplication.features.profile ├── ProfileActivity.kt └── ProfileSettingsActivity.kt
  33. droidcon Americas - @marcoGomier Modularized App └── features ├── home

    │ └── src │ └── main │ └── java │ └── com.prof18.myapplication.features.home │ ├── DashboardFragment.kt │ ├── HomeActivity.kt │ ├── HomeFragment.kt │ └── NotificationsFragment.kt └── profile └── src └── main └── java └── com.prof18.myapplication.features.profile ├── ProfileActivity.kt └── ProfileSettingsActivity.kt
  34. droidcon Americas - @marcoGomier Real Module Graph

  35. droidcon Americas - @marcoGomier More Real Module Graph

  36. droidcon Americas - @marcoGomier Module Graph apply from: 'https:"#raw.githubusercontent.com/JakeWharton/SdkSearch/master/gradle/projectDependencyGraph.gradle' ./gradlew

    projectDependencyGraph
  37. droidcon Americas - @marcoGomier How about navigation?

  38. droidcon Americas - @marcoGomier How about navigation? • Within a

    feature • Between features "Classic way", e.g. explicit intent, navigation component <Insert your favourite method here>
  39. droidcon Americas - @marcoGomier Implicit Intent How about navigation? •

    Within a feature • Between features "Classic way", e.g. explicit intent, navigation component
  40. droidcon Americas - @marcoGomier Between features navigation <activity android:name="com.prof18.filmatic.features.home.ui.HomeActivity" android:theme="@style/AppTheme.NoActionBar">

    <intent-filter> <action android:name="com.prof18.filmatic.features.home" "$ <category android:name="android.intent.category.DEFAULT" "$ "%intent-filter> "%activity> Declare the implicit intent in the Manifest
  41. droidcon Americas - @marcoGomier Between features navigation Declare the implicit

    intent in the Manifest <activity android:name="com.prof18.filmatic.features.home.ui.HomeActivity" android:theme="@style/AppTheme.NoActionBar"> <intent-filter> <action android:name="com.prof18.filmatic.features.home" "$ <category android:name="android.intent.category.DEFAULT" "$ "%intent-filter> "%activity>
  42. droidcon Americas - @marcoGomier Between features navigation Declare the implicit

    intent in the Manifest <activity android:name="com.prof18.filmatic.features.home.ui.HomeActivity" android:theme="@style/AppTheme.NoActionBar"> <intent-filter> <action android:name="com.prof18.filmatic.features.home" "$ <category android:name="android.intent.category.DEFAULT" "$ "%intent-filter> "%activity>
  43. droidcon Americas - @marcoGomier Between features navigation Create an object

    to help you with the boilerplate object Actions { fun openHomeIntent(context: Context): Intent = internalIntent(context, "com.prof18.filmatic.features.home") private fun internalIntent(context: Context, action: String) = Intent(action).setPackage(context.packageName) }
  44. droidcon Americas - @marcoGomier Between features navigation Create an object

    to help you with the boilerplate object Actions { fun openHomeIntent(context: Context): Intent = internalIntent(context, "com.prof18.filmatic.features.home") private fun internalIntent(context: Context, action: String) = Intent(action).setPackage(context.packageName) }
  45. droidcon Americas - @marcoGomier Between features navigation Create an object

    to help you with the boilerplate object Actions { fun openHomeIntent(context: Context): Intent = internalIntent(context, "com.prof18.filmatic.features.home") private fun internalIntent(context: Context, action: String) = Intent(action).setPackage(context.packageName) }
  46. droidcon Americas - @marcoGomier How to modularize an existent project?

  47. droidcon Americas - @marcoGomier Pull Code Up App

  48. droidcon Americas - @marcoGomier Pull Code Up App Old

  49. droidcon Americas - @marcoGomier Pull Code Up App Old App

  50. droidcon Americas - @marcoGomier Pull Code Up App Old App

    Core
  51. droidcon Americas - @marcoGomier Pull Code Up Old App Core

    Feature Future Feature App Old Future Library Feature Library
  52. droidcon Americas - @marcoGomier Pull Code Up App Old App

    Core Feature Future Library Feature Future Feature Library
  53. droidcon Americas - @marcoGomier Pull Code Up • Treat the

    old code "as a library" and then create new modules • A gentle process App Old App Core Feature Future Library Feature Future Feature Library
  54. droidcon Americas - @marcoGomier Pull Code Down App

  55. droidcon Americas - @marcoGomier App Pull Code Down Core

  56. droidcon Americas - @marcoGomier Future Feature Pull Code Down Core

    App Feature Future Library Feature Library
  57. droidcon Americas - @marcoGomier Pull Code Down Core App Feature

    Future Library Feature Future Feature Library
  58. droidcon Americas - @marcoGomier Pull Code Down • Hold the

    app together and then extract new modules • A more aggressive process Core App Feature Future Library Feature Future Feature Library
  59. droidcon Americas - @marcoGomier 1. Create new module core 2.

    All existing code into core 3. New app module that decides what to open 4. Extract a new feature from core 5. ""& Pull Code Up
  60. droidcon Americas - @marcoGomier 1. Create new module core 2.

    All existing code into core 3. New app module that decides what to open 4. Extract a new feature from core 5. ""& Pull Code Up
  61. droidcon Americas - @marcoGomier

  62. droidcon Americas - @marcoGomier 1. Maintain all the code in

    the existent app module 2. Move [some] common code to the core module 3. Start creating new features and libraries Change of strategy: Pull Code Down
  63. droidcon Americas @marcoGomier #4 Fail as fast as you can

  64. droidcon Americas - @marcoGomier • It's ok to fail during

    this process • Keep pushing • Allocate a big initial push to get things started Fail as fast as you can
  65. droidcon Americas @marcoGomier #5 Use Dependency Injection

  66. droidcon Americas - @marcoGomier Dagger in a multi-module project

  67. droidcon Americas - @marcoGomier Dagger in a multi-module project https:"#proandroiddev.com/using-dagger-in-

    a-multi-module-project-1e6af8f06ffc
  68. droidcon Americas - @marcoGomier Core Component @Component(modules = [CoreModule"'class, DataModule"'class])

    @Singleton interface CoreComponent { @Component.Builder interface Builder { fun build(): CoreComponent fun dataModule(dataModule: DataModule): Builder } "# Things that we want to expose fun getUserPreferences(): UserPreferences }
  69. droidcon Americas - @marcoGomier Feature Component @Component( modules = [HomeModule"'class],

    dependencies = [CoreComponent"'class] ) @FeatureScope interface HomeComponent { fun inject(fragment: ExploreFragment) fun inject(fragment: DiscoverFragment) fun inject(fragment: ProfileFragment) }
  70. droidcon Americas - @marcoGomier Core Component Provider interface CoreComponentProvider {

    fun provideCoreComponent(): CoreComponent }
  71. droidcon Americas - @marcoGomier Application class FilmaticApp : Application(), CoreComponentProvider

    { private lateinit var coreComponent: CoreComponent override fun provideCoreComponent(): CoreComponent { if (!this"'coreComponent.isInitialized) { coreComponent = DaggerCoreComponent .builder() .dataModule(DataModule(this)) .build() } return coreComponent } }
  72. droidcon Americas - @marcoGomier Core Component Provider object CoreInjectHelper {

    fun provideCoreComponent(applicationContext: Context): CoreComponent { return if (applicationContext is CoreComponentProvider) { (applicationContext as CoreComponentProvider).provideCoreComponent() } else { throw IllegalStateException(""(") } } }
  73. droidcon Americas - @marcoGomier Feature Activity DaggerHomeComponent .builder() .coreComponent(CoreInjectHelper.provideCoreComponent(applicationContext)) .build()

    .inject(this)
  74. https://developer.android.com/training/dependency-injection/hilt-multi-module https://developer.android.com/training/dependency-injection/hilt-android

  75. droidcon Americas - @marcoGomier Application @HiltAndroidApp class FilmaticApp : Application()

    { ""& }
  76. droidcon Americas - @marcoGomier Core Module[s] @InstallIn(ApplicationComponent"'class) @Module class CoreModule

    { ""& }
  77. droidcon Americas - @marcoGomier Feature Module[s] @Module @InstallIn(ActivityComponent"'class) class HomeModule

    { ""& }
  78. droidcon Americas - @marcoGomier Feature Activity @AndroidEntryPoint class HomeActivity :

    AppCompatActivity() { ""& }
  79. droidcon Americas - @marcoGomier https://github.com/prof18/Filmatic/commit/dbb03ef5c644682b1c512fd9ccbacabe06f5b940 Migration to Hilt

  80. droidcon Americas - @marcoGomier • Start gently with some [common]

    dependencies • Continue with more dependencies How to approach a no Dagger project?
  81. droidcon Americas @marcoGomier #6 Modules configurations

  82. droidcon Americas - @marcoGomier Modules configuration We need a smart

    way to handle dependencies and configurations across all modules
  83. droidcon Americas - @marcoGomier SDK Configuration ") buildSrc object Config

    { val minSdk = 21 val compileSdk = 30 val targetSdk = 30 val javaVersion = JavaVersion.VERSION_1_8 val buildTools = “30.0.1” }
  84. droidcon Americas - @marcoGomier SDK Configuration ") build.gradle subprojects {

    afterEvaluate { project ") if (project.hasProperty('android')) { android { buildToolsVersion Config.buildTools compileSdkVersion Config.compileSdk defaultConfig { minSdkVersion Config.minSdk targetSdkVersion Config.targetSdk testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } compileOptions { sourceCompatibility Config.javaVersion targetCompatibility Config.javaVersion } } } } }
  85. droidcon Americas - @marcoGomier SDK Configuration ") buildSrc import com.android.build.gradle.LibraryExtension

    import com.android.build.gradle.internal.dsl.BaseAppModuleExtension "# For app module fun BaseAppModuleExtension.applyAndroidConfig() { compileSdkVersion(Config.compileSdk) buildToolsVersion = Config.buildTools defaultConfig { ""& } } "# For library modules fun LibraryExtension.applyAndroidConfig() { compileSdkVersion(Config.compileSdk) buildToolsVersion = Config.buildTools defaultConfig { ""& } }
  86. droidcon Americas - @marcoGomier SDK Configuration ") buildSrc "# For

    app module fun BaseAppModuleExtension.applyAndroidConfig() { compileSdkVersion(Config.compileSdk) buildToolsVersion = Config.buildTools defaultConfig { ""& } } "# For library modules fun LibraryExtension.applyAndroidConfig() { compileSdkVersion(Config.compileSdk) buildToolsVersion = Config.buildTools defaultConfig { ""& } } import com.android.build.gradle.LibraryExtension import com.android.build.gradle.internal.dsl.BaseAppModuleExtension
  87. droidcon Americas - @marcoGomier SDK Configuration ") build.gradle.kts android {

    applyAndroidConfig() ""& }
  88. droidcon Americas - @marcoGomier Dependency Management ") buildSrc object Versions

    { val appcompat = "1.0.2" val design = "1.0.0" val cardview = "1.0.0" ""& } object Deps { val appcompat = "androidx.appcompat:appcompat:${Versions.appcompat}" val design = "com.google.android.material:material:${Versions.design}" val cardview = "androidx.cardview:cardview:${Versions.cardview}" ""& }
  89. droidcon Americas - @marcoGomier dependencies { implementation Deps.appcompat implementation Deps.retrofit

    implementation Deps.gson ""& } Dependency Management ") build.gradle
  90. droidcon Americas - @marcoGomier Common Dependencies ") shared_dependencies.gradle dependencies {

    implementation Deps.appcompat implementation Deps.constraintLayout implementation Deps.design implementation Deps.vectorDrawable implementation Deps.timber } apply from: '"(/"(/shared_dependencies.gradle'
  91. droidcon Americas - @marcoGomier Dependencies Update https://github.com/ben-manes/gradle-versions-plugin

  92. droidcon Americas - @marcoGomier ./gradlew dependencyUpdates ------------------------------------------------------------ : Project Dependency

    Updates (report to plain text file) ------------------------------------------------------------ The following dependencies are using the latest milestone version: - androidx.cardview:cardview:1.0.0 - androidx.constraintlayout:constraintlayout:2.0.0-beta3 - androidx.emoji:emoji-bundled:1.0.0 .... The following dependencies have later milestone versions: - androidx.appcompat:appcompat [1.1.0 ") 1.2.0-alpha01] https:"#developer.android.com/jetpack/androidx - androidx.browser:browser [1.0.0 ") 1.2.0-rc01] https:"#developer.android.com/jetpack/androidx - androidx.core:core-ktx [1.0.1 ") 1.2.0-rc01]
  93. droidcon Americas @marcoGomier #7 Decide your approach

  94. droidcon Americas - @marcoGomier What approach to follow? A big

    sprint? Small chunks of work?
  95. droidcon Americas - @marcoGomier It depends 0

  96. droidcon Americas - @marcoGomier • Start with preliminaries • move

    common code/resources to a [or many] core module[s] • Setup the navigation between features • ""& My approach
  97. droidcon Americas - @marcoGomier • Start by modularizing a single

    feature • Ship that feature [after testing, QA, etc. 1 ] • Start modularizing more features My approach
  98. droidcon Americas @marcoGomier #8 Create a pet project to try

    stuff
  99. droidcon Americas - @marcoGomier Architecture and modularization require an initial

    research
  100. droidcon Americas - @marcoGomier https://github.com/prof18/Filmatic

  101. droidcon Americas @marcoGomier Conclusions

  102. droidcon Americas - @marcoGomier • Plan a big initial push

    to get things started • It's a long [and difficult] journey but it totally worth it • Don't be afraid to fail Conclusions
  103. droidcon Americas Marco Gomiero Thank you! > Twitter: @marcoGomier >

    Github: prof18 > Website: marcogomiero.com