Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

Successfully modularizing your app

Jeroen Mols
October 03, 2019

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

Jeroen Mols

October 03, 2019
Tweet

More Decks by Jeroen Mols

Other Decks in Programming

Transcript

  1. WHY

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

    speed up builds • Avoid work/caching • Parallellization Need smaller items of work
  3. @MOLSJEROEN 2. ALLOW ON DEMAND DELIVERY At-install delivery On demand

    delivery Conditional delivery Instant delivery Need to split up app Support future requirements
  4. @MOLSJEROEN 3. SIMPLIFY DEVELOPMENT Clear contracts between modules • Less

    spaghetti code • Fewer cascading changes • Easier in-module refactoring Easy to understand code Cheaper maintenance
  5. @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
  6. @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
  7. @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
  8. @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
  9. @MOLSJEROEN APP 1 FEATURE 1 FEATURE 2 FEATURE 3 FEATURE

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

    4 FEATURE 5 FEATURE 6 SDK LIBRARY 2 4. REUSE MODULES: SDK
  11. @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
  12. @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
  13. @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
  14. @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
  15. @MOLSJEROEN 5. EXPERIMENT WITH NEW TECHNOLOGIES Rapidly evolving Android landscape

    Need to experiment rapidly Cost of wrong technology choice Avoid technology lock-in
  16. @MOLSJEROEN 6. SCALING Large teams cause new issues • Merge

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

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

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

    1 FEATURE 2 FEATURE 3 FEATURE 4 FEATURE 5 FEATURE 6
  20. @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
  21. @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
  22. @MOLSJEROEN 7. IMPROVE LEGACY CODE: REWRITE, REFACTOR OR ACCEPT APP

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

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

    code base Keep ability to release Gradually roll out possible Faster time to market
  25. @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
  26. YOU HAVE YOUR WAY. I HAVE MY WAY. THE RIGHT

    WAY, THE CORRECT WAY, AND THE ONLY WAY DOES NOT EXIST. F. Nietzsche
  27. @MOLSJEROEN ANDROID APP COMPONENTS Full screen UI Reuse across apps

    Started via explicit or implicit intents ACTIVITY SERVICE BROADCAST RECEIVER CONTENT PROVIDER
  28. @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
  29. @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
  30. @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
  31. @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
  32. @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
  33. @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
  34. @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
  35. @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
  36. @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>
  37. @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>
  38. @MOLSJEROEN NAVIGATION: BETWEEN FEATURES String duplication of action In depth

    knowledge of intent creation Other apps could also handle action
  39. @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
  40. @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())
  41. @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())
  42. @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"))
  43. @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"))
  44. @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) }
  45. @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) }
  46. @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
  47. @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())) } }
  48. @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())) } }
  49. @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())) } }
  50. @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())) } }
  51. @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
  52. @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
  53. @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
  54. @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
  55. @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
  56. @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
  57. @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*
  58. @MOLSJEROEN IMPROVE LEGACY: FEATURE FLAGS object Actions { fun openLoginIntent()

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

    = if (FeatureFlag.loginRewrite) { Intent("action.login2.open") } else { Intent("action.login.open") } }
  60. @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
  61. @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
  62. @MOLSJEROEN PULL CODE UP APP FEATURE FEATURE FEATURE FUTURE FEATURE

    FUTURE FEATURE FUTURE FEATURE OLD APP LIB LIBRARY FUTURE LIBRARY Pull modules up
  63. @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
  64. @MOLSJEROEN PUSH CODE DOWN APP FEATURE FEATURE FEATURE FUTURE FEATURE

    FUTURE FEATURE FUTURE FEATURE LIB 1 LIB FUTURE LIBRARY LIBRARY Push modules down
  65. @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
  66. @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
  67. @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
  68. @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
  69. @MOLSJEROEN CONFIGURE MODULES Adding a new module must be easy

    Maintaining module configurations must be easy
  70. @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 } } } } }
  71. @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 } } } } }
  72. @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 } } } } }
  73. @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 }
  74. @MOLSJEROEN ORGANIZE SETTINGS.GRADLE include ':app' include ':features:login' include ':features:dashboard' include

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

    ':features:sharing' include ':libraries:ui-components' include ':libraries:actions'
  76. @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
  77. @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
  78. @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}" ... }
  79. @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 }
  80. @MOLSJEROEN SPEED UP BUILDS dependencies { // Avoid doing this

    api project(':libraries:ui-components') // Do this instead implementation project(':libraries:ui-components') }
  81. THERE ARE NO BIG PROBLEMS, THERE ARE JUST A LOT

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

    WITHIN AND BETWEEN FEATURES PUSH CODE DOWN SIMPLIFY ADDING MODULES
  83. @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
  84. @MOLSJEROEN IMAGE CREDITS Welcome image by Clement127 https://flic.kr/p/n9ctTn Official Android

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