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

7+1 tips about [Android] App Modularization | d...

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.

Marco Gomiero

November 17, 2020
Tweet

More Decks by Marco Gomiero

Other Decks in Programming

Transcript

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

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

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

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

    development +# Split responsibilities , Reusable modules Source: https://speakerdeck.com/beraldofilippo/the-clean-cut?slide=10
  5. 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
  6. 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/
  7. droidcon Americas - @marcoGomier Monolithic Application └── app └── src

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

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

    └── main └── java └── com.prof18.myapplication ├── DashboardFragment.kt ├── HomeFragment.kt ├── MainActivity.kt ├── NotificationsFragment.kt ├── ProfileActivity.kt └── ProfileSettingsActivity.kt
  10. 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
  11. 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
  12. 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
  13. droidcon Americas - @marcoGomier Monolithic Application └── app └── src

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

    └── main └── java └── com.prof18.myapplication ├── MainActivity.kt
  15. 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
  16. 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
  17. 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
  18. 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
  19. droidcon Americas - @marcoGomier How about navigation? • Within a

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

    Within a feature • Between features "Classic way", e.g. explicit intent, navigation component
  21. 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
  22. 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>
  23. 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>
  24. 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) }
  25. 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) }
  26. 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) }
  27. droidcon Americas - @marcoGomier Pull Code Up Old App Core

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

    Core Feature Future Library Feature Future Feature Library
  29. 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
  30. droidcon Americas - @marcoGomier Future Feature Pull Code Down Core

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

    Future Library Feature Future Feature Library
  32. 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
  33. 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
  34. 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
  35. 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
  36. 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
  37. 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 }
  38. 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) }
  39. 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 } }
  40. 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(""(") } } }
  41. droidcon Americas - @marcoGomier • Start gently with some [common]

    dependencies • Continue with more dependencies How to approach a no Dagger project?
  42. droidcon Americas - @marcoGomier Modules configuration We need a smart

    way to handle dependencies and configurations across all modules
  43. 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” }
  44. 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 } } } } }
  45. 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 { ""& } }
  46. 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
  47. 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}" ""& }
  48. droidcon Americas - @marcoGomier dependencies { implementation Deps.appcompat implementation Deps.retrofit

    implementation Deps.gson ""& } Dependency Management ") build.gradle
  49. 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'
  50. 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]
  51. droidcon Americas - @marcoGomier • Start with preliminaries • move

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

    feature • Ship that feature [after testing, QA, etc. 1 ] • Start modularizing more features My approach
  53. 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