Successfully modularizing your app

Successfully modularizing your app

Video: https://www.youtube.com/watch?v=9eikhwWehWk

Modularizing your app seems to be all the hype these days. But why should you actually care? What are the benefits for you and your team? How should a modularized app look like? And how do you start splitting your app?

In this talk, you will learn why modularization matters and how a modularized architecture looks like. We'll study a real-life example project and investigate how modules are set up, organized and configuration is shared across modules. Finally, you'll learn how you can start modularizing your existing app and I'll share what lessons we learned while doing so at Philips Hue.

Articles:
1. why - http://bit.ly/modularization_why
2. architecture - http://bit.ly/modularization_architecture
3. example - http://bit.ly/modularization_example
4. how - http://bit.ly/modularization_how
5. lessons - http://bit.ly/modularization_lessons

Source code: http://bit.ly/modularization_code #androiddev #mobiconf

5f57d2d205e77e185986459c1b89a874?s=128

Jeroen Mols

October 03, 2019
Tweet

Transcript

  1. @MOLSJEROEN SUCCESSFULLY MODULARIZING APPS

  2. @MOLSJEROEN @MOLSJEROEN @MOLSJEROEN

  3. @MOLSJEROEN WHY MODULARIZATION ARCHITECTURE HOW TO APPROACH LESSONS LEARNED

  4. WHY

  5. @MOLSJEROEN 1. SPEED UP BUILDS Gradle does two things to

    speed up builds • Avoid work/caching • Parallellization Need smaller items of work
  6. @MOLSJEROEN 1. SPEED UP BUILDS: SMALLER ITEMS OF WORK

  7. @MOLSJEROEN 1. SPEED UP BUILDS: SINGLE MODULE

  8. @MOLSJEROEN 1. SPEED UP BUILDS: MULTI MODULE

  9. @MOLSJEROEN 1. SPEED UP BUILDS: MULTI MODULE

  10. @MOLSJEROEN 2. ALLOW ON DEMAND DELIVERY

  11. None
  12. @MOLSJEROEN 2. ALLOW ON DEMAND DELIVERY At-install delivery On demand

    delivery Conditional delivery Instant delivery Need to split up app Support future requirements
  13. @MOLSJEROEN 3. SIMPLIFY DEVELOPMENT APP

  14. @MOLSJEROEN 3. SIMPLIFY DEVELOPMENT APP FEATURE 1 FEATURE 2 FEATURE

    3 FEATURE 4 FEATURE 5 FEATURE 6
  15. IT’S EASIER TO BUILD SEVERAL SMALL THINGS THAN TO BUILD

    ONE BIG THING Unknown
  16. @MOLSJEROEN 3. SIMPLIFY DEVELOPMENT Clear contracts between modules • Less

    spaghetti code • Fewer cascading changes • Easier in-module refactoring Easy to understand code Cheaper maintenance
  17. @MOLSJEROEN 4. REUSE MODULES

  18. @MOLSJEROEN 4. REUSE MODULES: MULTIPLE APPS APP 1 FEATURE 1

    FEATURE 2 FEATURE 3 FEATURE 4 FEATURE 5 FEATURE 6 LIBRARY 1 LIBRARY 2 APP 2
  19. @MOLSJEROEN APP 1 FEATURE 1 FEATURE 2 FEATURE 3 FEATURE

    4 FEATURE 5 FEATURE 6 LIBRARY 1 LIBRARY 2 APP 2 4. REUSE MODULES: MULTIPLE APPS
  20. @MOLSJEROEN APP 1 FEATURE 1 FEATURE 2 FEATURE 3 FEATURE

    4 FEATURE 5 FEATURE 6 LIBRARY 1 LIBRARY 2 APP 2 FEATURE X FEATURE X 4. REUSE MODULES: MULTIPLE APPS
  21. @MOLSJEROEN APP 1 FEATURE 1 FEATURE 2 FEATURE 3 FEATURE

    4 FEATURE 5 FEATURE 6 LIBRARY 1 LIBRARY 2 APP 2 FEATURE X FEATURE X 4. REUSE MODULES: MULTIPLE APPS
  22. @MOLSJEROEN APP 1 FEATURE 1 FEATURE 2 FEATURE 3 FEATURE

    4 FEATURE 5 FEATURE 6 SDK LIBRARY 2 4. REUSE MODULES: SDK
  23. @MOLSJEROEN APP 1 FEATURE 1 FEATURE 2 FEATURE 3 FEATURE

    4 FEATURE 5 FEATURE 6 SDK LIBRARY 2 4. REUSE MODULES: SDK
  24. @MOLSJEROEN APP 1 EXPOSED FEATURE WITH INTENT FEATURE 2 FEATURE

    3 FEATURE 4 FEATURE 5 FEATURE 6 LIBRARY 1 LIBRARY 2 4. REUSE MODULES: EXPOSE FEATURES
  25. @MOLSJEROEN APP 1 EXPOSED FEATURE WITH INTENT FEATURE 2 FEATURE

    3 FEATURE 4 FEATURE 5 FEATURE 6 LIBRARY 1 LIBRARY 2 4. REUSE MODULES: EXPOSE FEATURES
  26. @MOLSJEROEN APP 1 FEATURE 1 FEATURE 2 FEATURE 3 FEATURE

    4 FEATURE 5 FEATURE 6 AWESOME GITHUB LIBRARY LIBRARY 2 4. REUSE MODULES: OPEN SOURCE
  27. @MOLSJEROEN APP 1 FEATURE 1 FEATURE 2 FEATURE 3 FEATURE

    4 FEATURE 5 FEATURE 6 AWESOME GITHUB LIBRARY LIBRARY 2 4. REUSE MODULES: OPEN SOURCE
  28. @MOLSJEROEN 5. EXPERIMENT WITH NEW TECHNOLOGIES Rapidly evolving Android landscape

    Need to experiment rapidly Cost of wrong technology choice Avoid technology lock-in
  29. @MOLSJEROEN Source: https://blog.daftcode.pl/hype-driven-development-3469fc2e9b22

  30. @MOLSJEROEN 5. EXPERIMENT WITH NEW TECHNOLOGIES APP FEATURE 1 FEATURE

    2 FEATURE 3 FEATURE 4 FEATURE 5 FEATURE 6
  31. @MOLSJEROEN 6. SCALING Large teams cause new issues • Merge

    conflicts • Dependencies • Code ownership
  32. @MOLSJEROEN 6. SCALING: DEVELOP IN PARALLEL Team A Team B

    APP FEATURE 1 FEATURE 2 FEATURE 3 FEATURE 4 FEATURE 5 FEATURE 6
  33. @MOLSJEROEN 6. SCALING: DEVELOP IN PARALLEL Team A Team B

    APP FEATURE 1 FEATURE 2 FEATURE 3 FEATURE 4 FEATURE 5 FEATURE 6
  34. @MOLSJEROEN 6. SCALING: OUTSOURCE Team A External company APP FEATURE

    1 FEATURE 2 FEATURE 3 FEATURE 4 FEATURE 5 FEATURE 6
  35. @MOLSJEROEN 7. IMPROVE LEGACY CODE

  36. @MOLSJEROEN Aggressive High impact Lot of work Not value focussed

    Risky launch Exciting Less risky Gradual Slow Tedious Continuous releases Unsatisfying REWRITE REFACTOR V S
  37. @MOLSJEROEN Aggressive High impact Lot of work Not value focussed

    Risky launch Exciting Less risky Gradual Slow Tedious Continuous releases Unsatisfying REWRITE REFACTOR V S
  38. @MOLSJEROEN 7. IMPROVE LEGACY CODE: REWRITE, REFACTOR OR ACCEPT APP

    FEATURE 1 FEATURE 2 FEATURE 3 FEATURE 4 FEATURE 5 FEATURE 6
  39. @MOLSJEROEN 7. IMPROVE LEGACY CODE: REWRITE, REFACTOR OR ACCEPT APP

    FEATURE 1 FEATURE 2 FEATURE 3 FEATURE 4 FEATURE 5 FEATURE 6
  40. @MOLSJEROEN 7. IMPROVE LEGACY CODE Able to rewrite parts of

    code base Keep ability to release Gradually roll out possible Faster time to market
  41. @MOLSJEROEN 8. SIMPLIFY TEST AUTOMATION

  42. @MOLSJEROEN 8. SIMPLIFY TEST AUTOMATION

  43. @MOLSJEROEN 8. SIMPLIFY TEST AUTOMATION Speeds up tests Simplified test

    setup Higher test reliability
  44. @MOLSJEROEN WHY: RECAP Speed up builds Allow on demand delivery

    Simplify development Reuse modules across apps Experiment with new technologies Scale development team Improve legacy code Simplify test automation
  45. MODULARIZATION ARCHITECTURE

  46. YOU HAVE YOUR WAY. I HAVE MY WAY. THE RIGHT

    WAY, THE CORRECT WAY, AND THE ONLY WAY DOES NOT EXIST. F. Nietzsche
  47. @MOLSJEROEN APP STRUCTURE

  48. @MOLSJEROEN APP STRUCTURE

  49. @MOLSJEROEN APP STRUCTURE

  50. @MOLSJEROEN APP STRUCTURE

  51. FEATURES ARE A FLOW OF SCREENS

  52. @MOLSJEROEN APP WIREFRAME

  53. @MOLSJEROEN APP WIREFRAME

  54. @MOLSJEROEN APP WIREFRAME

  55. APPS CONSIST OF SEVERAL FEATURES

  56. @MOLSJEROEN ANDROID APP COMPONENTS ACTIVITY SERVICE BROADCAST RECEIVER CONTENT PROVIDER

  57. @MOLSJEROEN ANDROID APP COMPONENTS Full screen UI Reuse across apps

    Started via explicit or implicit intents ACTIVITY SERVICE BROADCAST RECEIVER CONTENT PROVIDER
  58. @MOLSJEROEN IMPLICIT INTENTS

  59. ANDROID LINKS MULTIPLE APPS TOGETHER VIA IMPLICIT INTENTS

  60. @MOLSJEROEN MODULARIZED ARCHITECTURE APP

  61. @MOLSJEROEN MODULARIZED ARCHITECTURE APP FEATURE 1 FEATURE 2 FEATURE 3

    FEATURE 4 FEATURE 5 FEATURE 6
  62. @MOLSJEROEN MODULARIZED ARCHITECTURE APP FEATURE 1 FEATURE 2 FEATURE 3

    FEATURE 4 FEATURE 5 FEATURE 6 LIBRARY 4 LIBRARY 4 LIB 1 LIBRARY 2 LIBRARY 3
  63. @MOLSJEROEN FEATURE MODULES Full screen, coherent user facing functionality in

    the app Android library module Single activity with (optional) navigation graph Respond to implicit intents and pass back a result Never depend on other features or app Depend on several library modules APP FEATURE 1 FEATURE 2 FEATURE 3 FEATURE 4 FEATURE 5 FEATURE 6 LIBRARY 4 LIBRARY 4 LIB 1 LIBRARY 2 LIBRARY 3
  64. @MOLSJEROEN LIBRARY MODULES Plumbing that is reused across several or

    all features Android library, pure Java or pure Kotlin module Never depend on features or app Can (but don’t have to) depend on other libraries APP FEATURE 1 FEATURE 2 FEATURE 3 FEATURE 4 FEATURE 5 FEATURE 6 LIBRARY 4 LIBRARY 4 LIB 1 LIBRARY 2 LIBRARY 3
  65. @MOLSJEROEN APP MODULE Links feature modules together in a useful

    app Android application module Depends on other features and libraries Orchestrates navigation between features Decides what features are enabled using feature toggles Doesn’t contain much code APP FEATURE 1 FEATURE 2 FEATURE 3 FEATURE 4 FEATURE 5 FEATURE 6 LIBRARY 4 LIBRARY 4 LIB 1 LIBRARY 2 LIBRARY 3
  66. @MOLSJEROEN ADVANTAGES Application structure Navigation Testing Scaling Feature flagging Experiment

    with new technology
  67. @MOLSJEROEN APPLICATION STRUCTURE

  68. @MOLSJEROEN DASHBOARD FEATURE

  69. @MOLSJEROEN DASHBOARD ACTIVITY

  70. @MOLSJEROEN LOGIN FEATURE

  71. @MOLSJEROEN SHARING FEATURE

  72. @MOLSJEROEN NAVIGATION Split navigation in smaller parts • Within a

    feature -> handled by the feature • Between features -> handled by the app module APP FEATURE 1 FEATURE 2 FEATURE 3 FEATURE 4 FEATURE 5 FEATURE 6
  73. @MOLSJEROEN NAVIGATION: IN FEATURE Android Navigation components • Visualize navigation

    flow • Find back screens • Easy code browsing
  74. @MOLSJEROEN NAVIGATION: IN FEATURE

  75. @MOLSJEROEN <fragment xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/nav_host_fragment" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="match_parent" android:layout_height="match_parent" app:defaultNavHost="true" app:navGraph="@navigation/login_graph"/>

    NAVIGATION: IN FEATURE
  76. @MOLSJEROEN <fragment xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/nav_host_fragment" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="match_parent" android:layout_height="match_parent" app:defaultNavHost="true" app:navGraph="@navigation/login_graph"/>

    NAVIGATION: IN FEATURE
  77. @MOLSJEROEN <navigation android:id="@+id/login_graph" app:startDestination="@id/welcomeFragment"> <fragment android:id="@+id/welcomeFragment" android:name="modularization.features.login.WelcomeFragment" /> <fragment android:id="@+id/loginFragment"

    android:name="modularization.features.login.LoginFragment" /> <fragment android:id="@+id/avatarFragment" android:name="modularization.features.login.AvatarFragment"/> </navigation> NAVIGATION: IN FEATURE
  78. @MOLSJEROEN <navigation android:id="@+id/login_graph" app:startDestination="@id/welcomeFragment"> <fragment android:id="@+id/welcomeFragment" android:name="modularization.features.login.WelcomeFragment" /> <fragment android:id="@+id/loginFragment"

    android:name="modularization.features.login.LoginFragment" /> <fragment android:id="@+id/avatarFragment" android:name="modularization.features.login.AvatarFragment"/> </navigation> NAVIGATION: IN FEATURE
  79. @MOLSJEROEN <navigation android:id="@+id/login_graph" app:startDestination="@id/welcomeFragment"> <fragment android:id="@+id/welcomeFragment" android:name="modularization.features.login.WelcomeFragment" /> <fragment android:id="@+id/loginFragment"

    android:name="modularization.features.login.LoginFragment" /> <fragment android:id="@+id/avatarFragment" android:name="modularization.features.login.AvatarFragment"/> </navigation> NAVIGATION: IN FEATURE
  80. @MOLSJEROEN <navigation //... <fragment android:id="@+id/welcomeFragment" android:name="modularization.features.login.WelcomeFragment" > <action android:id="@+id/action_welcomeFragment_to_loginFragment" app:destination="@id/loginFragment"

    /> </fragment> <fragment android:id="@+id/loginFragment" /> <fragment android:id="@+id/avatarFragment" /> </navigation> NAVIGATION: IN FEATURE
  81. @MOLSJEROEN <navigation //... <fragment android:id="@+id/welcomeFragment" android:name="modularization.features.login.WelcomeFragment" > <action android:id="@+id/action_welcomeFragment_to_loginFragment" app:destination="@id/loginFragment"

    /> </fragment> <fragment android:id="@+id/loginFragment" /> <fragment android:id="@+id/avatarFragment" /> </navigation> NAVIGATION: IN FEATURE
  82. @MOLSJEROEN findNavController().navigate(R.id.action_welcomeFragment_to_loginFragment) NAVIGATION: IN FEATURE

  83. @MOLSJEROEN NAVIGATION: IN FEATURE

  84. @MOLSJEROEN NAVIGATION: BETWEEN FEATURES startActivity(Intent(activity, DashboardActivity::class.java))

  85. @MOLSJEROEN NAVIGATION: BETWEEN FEATURES startActivity(Intent("action.dashboard.open"))

  86. @MOLSJEROEN NAVIGATION: BETWEEN FEATURES <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="modularization.dashboard"> <application android:theme="@style/AppTheme" >

    <activity android:name=".DashboardActivity"> <intent-filter> <action android:name="action.dashboard.open"/> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> </application> </manifest>
  87. @MOLSJEROEN NAVIGATION: BETWEEN FEATURES <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="modularization.dashboard"> <application android:theme="@style/AppTheme" >

    <activity android:name=".DashboardActivity"> <intent-filter> <action android:name="action.dashboard.open"/> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> </application> </manifest>
  88. @MOLSJEROEN NAVIGATION: BETWEEN FEATURES String duplication of action In depth

    knowledge of intent creation Other apps could also handle action
  89. @MOLSJEROEN NAVIGATION: BETWEEN FEATURES APP FEATURE 1 FEATURE 2 FEATURE

    3 FEATURE 4 FEATURE 5 FEATURE 6 ACTIONS LIBRARY 4 LIB 1 LIBRARY 2 LIBRARY 3
  90. @MOLSJEROEN NAVIGATION: BETWEEN FEATURES object Actions { fun openLoginIntent() =

    Intent("action.login.open") fun openDashboardIntent() = Intent("action.dashboard.open") fun openSharingIntent() = Intent("action.sharing.open") } activity.startActivity(Actions.openDashboardIntent())
  91. @MOLSJEROEN NAVIGATION: BETWEEN FEATURES object Actions { fun openLoginIntent() =

    Intent("action.login.open") fun openDashboardIntent() = Intent("action.dashboard.open") fun openSharingIntent() = Intent("action.sharing.open") } activity.startActivity(Actions.openDashboardIntent())
  92. @MOLSJEROEN NAVIGATION: BETWEEN FEATURES object Actions { fun openDashboardIntent(userId: String)

    = Intent(context, "action.dashboard.open") .putExtra(EXTRA_USER, UserArgs(userId)) } activity.startActivity(Actions.openDashboardIntent("userId"))
  93. @MOLSJEROEN NAVIGATION: BETWEEN FEATURES object Actions { fun openDashboardIntent(userId: String)

    = Intent(context, "action.dashboard.open") .putExtra(EXTRA_USER, UserArgs(userId)) } activity.startActivity(Actions.openDashboardIntent("userId"))
  94. @MOLSJEROEN NAVIGATION: BETWEEN FEATURES

  95. @MOLSJEROEN NAVIGATION: BETWEEN FEATURES object Actions { fun openLoginIntent(context: Context)

    = internalIntent(context, "action.login.open") private fun internalIntent(context: Context, action: String) = Intent(action).setPackage(context.packageName) }
  96. @MOLSJEROEN NAVIGATION: BETWEEN FEATURES object Actions { fun openLoginIntent(context: Context)

    = internalIntent(context, "action.login.open") private fun internalIntent(context: Context, action: String) = Intent(action).setPackage(context.packageName) }
  97. @MOLSJEROEN NAVIGATION: BETWEEN FEATURES Implicit intents Actions module Abstract away

    feature parameters Restrict to current package
  98. @MOLSJEROEN TESTING UNIT INTEGRATION E2E

  99. @MOLSJEROEN TESTING APP FEATURE 1 FEATURE 2 FEATURE 3 FEATURE

    4 FEATURE 5 FEATURE 6 LIBRARY 4 LIBRARY 4 LIB 1 LIBRARY 2 LIBRARY 3
  100. @MOLSJEROEN FEATURE TESTING class LoginFlowTest { @Rule @JvmField var mActivityTestRule

    = ActivityTestRule(LoginActivity::class.java) @Test fun loginFlowTest() { onView(withId(R.id.button_login_start)).perform(click()) onView(withId(R.id.button_login_signin)).perform(click()) onView(withId(R.id.button_login_toapp)).check(matches(isDisplayed())) } }
  101. @MOLSJEROEN FEATURE TESTING class LoginFlowTest { @Rule @JvmField var mActivityTestRule

    = ActivityTestRule(LoginActivity::class.java) @Test fun loginFlowTest() { onView(withId(R.id.button_login_start)).perform(click()) onView(withId(R.id.button_login_signin)).perform(click()) onView(withId(R.id.button_login_toapp)).check(matches(isDisplayed())) } }
  102. @MOLSJEROEN FEATURE TESTING class LoginFlowTest { @Rule @JvmField var mActivityTestRule

    = ActivityTestRule(LoginActivity::class.java) @Test fun loginFlowTest() { onView(withId(R.id.button_login_start)).perform(click()) onView(withId(R.id.button_login_signin)).perform(click()) onView(withId(R.id.button_login_toapp)).check(matches(isDisplayed())) } }
  103. @MOLSJEROEN APP FLOW TESTING class AppFlowTest { @Rule @JvmField var

    mActivityTestRule = ActivityTestRule(MainActivity::class.java) @Test fun test_criticalUserFlow_throughoutEntireApp() { onView(withId(modularization.login.R.id.button_login_start)).perform(click()) onView(withId(modularization.login.R.id.button_login_signin)).perform(click()) onView(withId(modularization.login.R.id.button_login_toapp)).perform(click()) onView(withId(R.id.action_albums)).perform(click()) onView(withId(R.id.action_sharing)).perform(click()) onView(withId(R.id.button_social_facebook)).perform(click()) onView(withId(R.id.recyclerView_sharing_contacts)).check(matches(isDisplayed())) } }
  104. @MOLSJEROEN SCALING: TEAMS Team A Team B APP FEATURE 1

    FEATURE 2 FEATURE 3 FEATURE 4 FEATURE 5 FEATURE 6 LIBRARY 4 LIBRARY 4 LIB 1 LIBRARY 2 LIBRARY 3
  105. @MOLSJEROEN APP 1 FEATURE 1 FEATURE 2 FEATURE 3 FEATURE

    4 FEATURE 5 FEATURE 6 LIBRARY 1 LIBRARY 2 APP 2 FEATURE X FEATURE X SCALING: APPS
  106. @MOLSJEROEN SCALING: SDKS APP EXPOSED FEATURE WITH INTENT FEATURE 2

    FEATURE 3 FEATURE 4 FEATURE 5 FEATURE 6 LIBRARY 4 AWESOME GITHUB LIBRARY LIB 1 SDK LIBRARY 3
  107. @MOLSJEROEN SCALING: TECHNOLOGY APP FEATURE 1 FEATURE 2 FEATURE 3

    FEATURE 4 FEATURE 5 FEATURE 6 LIBRARY 4 LIBRARY 4 LIB 1 LIBRARY 2 LIBRARY 3
  108. @MOLSJEROEN IMPROVE LEGACY APP FEATURE 1 FEATURE 2 FEATURE 3

    FEATURE 4 FEATURE 5 FEATURE 6 LIBRARY 4 LIBRARY 4 LIB 1 LIBRARY 2 LIBRARY 3
  109. @MOLSJEROEN IMPROVE LEGACY: REWRITE FEATURE APP FEATURE 1 FEATURE 2

    FEATURE 3 FEATURE 4 FEATURE 5 FEATURE 6 LIBRARY 4 LIBRARY 4 LIB 1 LIBRARY 2 LIBRARY 3
  110. @MOLSJEROEN IMPROVE LEGACY: REWRITE FEATURE APP FEATURE 1 FEATURE 2

    FEATURE 3 FEATURE 4 FEATURE 5 FEATURE 6 LIBRARY 4 LIBRARY 4 LIB 1 LIBRARY 2 LIBRARY 3 FEATURE 5*
  111. @MOLSJEROEN IMPROVE LEGACY: FEATURE FLAGS object Actions { fun openLoginIntent()

    = if (FeatureFlag.loginRewrite) { Intent("action.login2.open") } else { Intent("action.login.open") } }
  112. @MOLSJEROEN IMPROVE LEGACY: FEATURE FLAGS object Actions { fun openLoginIntent()

    = if (FeatureFlag.loginRewrite) { Intent("action.login2.open") } else { Intent("action.login.open") } }
  113. @MOLSJEROEN IMPROVE LEGACY: ROLL OUT

  114. @MOLSJEROEN

  115. @MOLSJEROEN ARCHITECTURE: RECAP Easy code navigation Simple in-feature and across

    feature navigation Better test automation Enables scaling Able to experiment with new technologies Staged rollout of rewritten features
  116. HOW TO APPROACH

  117. @MOLSJEROEN APP FEATURE FEATURE FEATURE FUTURE. FEATURE FUTURE FEATURE FUTURE

    FEATURE OLD APP LIB LIBRARY FUTURE LIBRARY APP FEATURE FEATURE FEATURE FUTURE FEATURE FUTURE FEATURE FUTURE FEATURE LIB 1 LIB FUTURE LIBRARY LIBRARY CHOOSE YOUR SIDE
  118. @MOLSJEROEN PULL CODE UP APP FEATURE FEATURE FEATURE FUTURE FEATURE

    FUTURE FEATURE FUTURE FEATURE OLD APP LIB LIBRARY FUTURE LIBRARY Pull modules up
  119. @MOLSJEROEN PULL CODE UP Conceptually very simple Extract features without

    dependency issues Huge upfront change Encourages gradual modularization Incompatible with Butterknife: change to R2.id.*** Features need to be extracted before libraries
  120. @MOLSJEROEN PUSH CODE DOWN APP FEATURE FEATURE FEATURE FUTURE FEATURE

    FUTURE FEATURE FUTURE FEATURE LIB 1 LIB FUTURE LIBRARY LIBRARY Push modules down
  121. @MOLSJEROEN PUSH CODE DOWN Initially a lot harder Extract libraries

    without dependency issues No big upfront change Forces aggressive modularisation Better grip on modularisation process Bottom up code clean up
  122. @MOLSJEROEN APP FEATURE FEATURE FEATURE FUTURE. FEATURE FUTURE FEATURE FUTURE

    FEATURE OLD APP LIB LIBRARY FUTURE LIBRARY APP FEATURE FEATURE FEATURE FUTURE FEATURE FUTURE FEATURE FUTURE FEATURE LIB 1 LIB FUTURE LIBRARY LIBRARY CHOOSE YOUR SIDE
  123. @MOLSJEROEN APP FEATURE FEATURE FEATURE FUTURE. FEATURE FUTURE FEATURE FUTURE

    FEATURE OLD APP LIB LIBRARY FUTURE LIBRARY APP FEATURE FEATURE FEATURE FUTURE FEATURE FUTURE FEATURE FUTURE FEATURE LIB 1 LIB FUTURE LIBRARY LIBRARY CHOOSE YOUR SIDE
  124. @MOLSJEROEN CONSIDERATIONS Make a big initial push Aggressively restrict visibility

    Combine with general code improvements Postpone/avoid problems • Cut dependencies using abstractions • Deprecate and rewrite • Clean up internals later
  125. WHEN PLANNING IS LONG THEN IT’S WRONG, IF IT’S TIGHT

    THEN IT’S RIGHT Elon musk
  126. LESSONS LEARNED

  127. @MOLSJEROEN CONFIGURE MODULES Adding a new module must be easy

    Maintaining module configurations must be easy
  128. @MOLSJEROEN CONFIGURE MODULES subprojects { afterEvaluate { project -> if

    (project.hasProperty('android')) { android { buildToolsVersion Config.buildTools compileSdkVersion Config.compileSdk defaultConfig { minSdkVersion Config.minSdk targetSdkVersion Config.targetSdk } compileOptions { sourceCompatibility Config.javaVersion targetCompatibility Config.javaVersion } } } } }
  129. @MOLSJEROEN CONFIGURE MODULES subprojects { afterEvaluate { project -> if

    (project.hasProperty('android')) { android { buildToolsVersion Config.buildTools compileSdkVersion Config.compileSdk defaultConfig { minSdkVersion Config.minSdk targetSdkVersion Config.targetSdk } compileOptions { sourceCompatibility Config.javaVersion targetCompatibility Config.javaVersion } } } } }
  130. @MOLSJEROEN CONFIGURE MODULES subprojects { afterEvaluate { project -> if

    (project.hasProperty('android')) { android { buildToolsVersion Config.buildTools compileSdkVersion Config.compileSdk defaultConfig { minSdkVersion Config.minSdk targetSdkVersion Config.targetSdk } compileOptions { sourceCompatibility Config.javaVersion targetCompatibility Config.javaVersion } } } } }
  131. @MOLSJEROEN CONFIGURE MODULES apply plugin: 'com.android.library' apply plugin: 'kotlin-android-extensions' apply

    plugin: 'kotlin-android' dependencies { implementation project(':libraries:ui-components') implementation project(':libraries:actions') implementation Deps.androidx_material implementation Deps.androidx_constraintlayout implementation Deps.androidx_navigation_fragment implementation Deps.androidx_navigation_ui testImplementation Deps.testlib_junit androidTestImplementation Deps.testandroidx_runner androidTestImplementation Deps.testandroidx_rules androidTestImplementation Deps.testandroidx_espressocore }
  132. @MOLSJEROEN CONFIGURE MODULES

  133. @MOLSJEROEN MODULE GRAPH apply from: 'https://raw.githubusercontent.com/JakeWharton/SdkSearch/master/ gradle/projectDependencyGraph.gradle' ./gradlew projectDependencyGraph

  134. @MOLSJEROEN MODULE GRAPH

  135. @MOLSJEROEN ORGANIZE SETTINGS.GRADLE include ':app', ':features:login', ':features:dashboard', ':features:sharing', ':libraries:ui- components',

    ':libraries:actions'
  136. @MOLSJEROEN ORGANIZE SETTINGS.GRADLE include ':app' include ':features:login' include ':features:dashboard' include

    ':features:sharing' include ':libraries:ui-components' include ':libraries:actions'
  137. @MOLSJEROEN ORGANIZE SETTINGS.GRADLE include ':app' include ':features:login' include ':features:dashboard' include

    ':features:sharing' include ':libraries:ui-components' include ':libraries:actions'
  138. @MOLSJEROEN MODULE INTERNALS

  139. @MOLSJEROEN MODULE INTERNALS - ORGANIZATION

  140. @MOLSJEROEN MODULE INTERNALS - ORGANIZATION

  141. @MOLSJEROEN MODULE INTERNALS - PACKAGE NAME features: [project-name].features.[feature-name]
 e.g. modularization.features.login

    libraries: [project-name].libraries.[library-name]
 e.g. modularization.libraries.actions
  142. @MOLSJEROEN MODULE INTERNALS - PACKAGE NAME import android.content.Intent import android.os.Bundle

    import androidx.appcompat.app.AppCompatActivity import modularization.features.login.LoginActivity import modularization.features.sharing.SharingActivity import modularization.features.dashboard.DashboardActivity import modularization.libraries.actions.Actions import modularization.libraries.uicomponents.RoundedButton
  143. @MOLSJEROEN LAYOUT PREVIEWS

  144. @MOLSJEROEN LAYOUT PREVIEWS

  145. @MOLSJEROEN LAYOUT PREVIEWS <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="modularization.features.dashboard"> <application android:theme="@style/AppTheme"> <activity android:name=".DashboardActivity"

    /> </application> </manifest>
  146. @MOLSJEROEN LAYOUT PREVIEWS <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="modularization.features.dashboard"> <application android:theme="@style/AppTheme"> <activity android:name=".DashboardActivity"

    /> </application> </manifest>
  147. @MOLSJEROEN LAYOUT PREVIEWS

  148. @MOLSJEROEN DEPENDENCY MANAGEMENT object Config { val minSdk = 23

    val javaVersion = JavaVersion.VERSION_1_8 val buildTools = "28.0.3" } object Versions { val androidx_core = "1.0.1" val androidx_nav = "2.0.0" ... } object Deps { val androidx_core = "androidx.core:core-ktx:${Versions.androidx_core}" val androidx_nav_fragment = "androidx.navigation:navigation-fragment-ktx:${Versions.androidx_nav}" val androidx_nav_ui = “androidx.navigation:navigation-ui-ktx:${Versions.androidx_nav}" ... }
  149. @MOLSJEROEN CONFIGURE MODULES apply plugin: 'com.android.library' apply plugin: 'kotlin-android-extensions' apply

    plugin: 'kotlin-android' dependencies { implementation project(':libraries:ui-components') implementation project(':libraries:actions') implementation Deps.androidx_material implementation Deps.androidx_constraintlayout implementation Deps.androidx_navigation_fragment implementation Deps.androidx_navigation_ui testImplementation Deps.testlib_junit androidTestImplementation Deps.testandroidx_runner androidTestImplementation Deps.testandroidx_rules androidTestImplementation Deps.testandroidx_espressocore }
  150. @MOLSJEROEN DEPENDENCY MANAGEMENT ./gradlew dependencyUpdates

  151. @MOLSJEROEN SPEED UP BUILDS dependencies { // Avoid doing this

    api project(':libraries:ui-components') // Do this instead implementation project(':libraries:ui-components') }
  152. @MOLSJEROEN API VS IMPLEMENTATION DEPENDENCY APP

  153. @MOLSJEROEN API DEPENDENCY APP

  154. @MOLSJEROEN API DEPENDENCY APP

  155. @MOLSJEROEN API DEPENDENCY APP

  156. @MOLSJEROEN IMPLEMENTATION DEPENDENCY APP

  157. WRAP UP

  158. THERE ARE NO BIG PROBLEMS, THERE ARE JUST A LOT

    OF LITTLE PROBLEMS. Henry Ford
  159. @MOLSJEROEN MODULARIZATION HAS TONS OF BENEFITS APP FEATURES LIBRARIES NAVIGATION

    WITHIN AND BETWEEN FEATURES PUSH CODE DOWN SIMPLIFY ADDING MODULES
  160. @MOLSJEROEN

  161. @MOLSJEROEN HTTPS://JEROENMOLS.COM/BLOG

  162. @MOLSJEROEN REFERENCES Blog posts: 1. http://bit.ly/modularization_why 2. http://bit.ly/modularization_architecture 3. http://bit.ly/modularization_example

    4. http://bit.ly/modularization_how 5. http://bit.ly/modularization_lessons http://bit.ly/modularization_code
  163. @MOLSJEROEN IMAGE CREDITS Welcome image by Clement127 https://flic.kr/p/n9ctTn Official Android

    documentation https://d.android.com Font awesome https://fontawesome.com/
  164. MOLSJEROEN