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

Modularise in Structure

Modularise in Structure

Modularisation has been a big topic in last few years and there has been a lot of materials out there explaining why you should modularise your application. So, this talk is not one of them.

Everyday, more and more people build their application in modules. Either, you have been already working on this subject or you will work on it soon enough. Once you start modularising, it goes incredibly fast and you can end up with a lot of modules.

How to keep it under control though? What are the chances you will end up everything depending on everything, and your build time is even worse? How can you prevent such mess to happen?

In this talk, you will learn how to modularise feature based and structure in layers, how to optimise your gradle configurations to unify and simplify your module configuration and how all these can work in harmony.

- Presented in DevFest Hamburg (02.11.2019)
- Presented in DevFest Bucharest (15.11.2019)
- Presented in Droidcon Madrid (20.12.2019)

Yahya Bayramoğlu

November 02, 2019
Tweet

More Decks by Yahya Bayramoğlu

Other Decks in Programming

Transcript

  1. Why Modularization? • Modularizing Android Applications by Marvin Ramin •

    Best practices for a modularized app by Ben Weiss • Patchwork Plaid  -  A modularization story by Ben Weiss • Modularization - Why you should care by Jeroen Mols • How Yelp Modularized the Android App by Sanae Rosen • And many more... This talk is not about it :) but...
  2. Why Modularization? • Parallel build • Gradle build cache ◦

    Only changed modules re-compiled ◦ Incremental build time decrease • Clear scope & encapsulation • Putting more thought in development • Less entangled codebase ◦ As long as it is possible, we will do without even noticing ◦ Modularization makes it a bit more difficult • Kotlin and internal visibility
  3. Structure Application Feature 1 Feature 2 Feature 3 Logging Experiments

    Navigation Network SubFeature4 SubFeature5 SubFeature3 SubFeature2 SubFeature1 Architecture Dependency Injection
  4. Structure Application Feature 1 Feature 2 Feature 3 Logging Experiments

    Navigation Network SubFeature4 SubFeature5 SubFeature3 SubFeature2 SubFeature1 Architecture Dependency Injection
  5. Structure Application Feature 1 Feature 2 Feature 3 Logging Experiments

    Navigation Network SubFeature4 SubFeature5 SubFeature3 SubFeature2 SubFeature1 Architecture Dependency Injection
  6. Structure • How to create horizontal layers? • How can

    features not depend each other? • How about sharing some code between features? • How about common features that are used within multiple features? • How to navigate without depending? • ...
  7. Feature Based Modules • Keeps the integrity of code •

    Easy to follow dependencies • Easy to extract / expose • Dynamic features
  8. Horizontal Layers Top to bottom dependency only / Unidirectional dependency

    • Core: such as Architecture, Networking, Logging, etc. • Testing: Testing framework for any core library • Features: Anything potentially can live by its own and exposed ◦ Profile ◦ Message ◦ Feed ◦ ...
  9. Features cannot depend each other in same layer • Vertical

    independency • Key to extract / expose easily • Dynamic features requirements
  10. Features cannot depend each other in same layer Application Feature

    1 Feature 2 Feature 3 Logging Experiments Navigation Network SubFeature4 SubFeature5 SubFeature3 SubFeature2 SubFeature1 Architecture Dependency Injection
  11. Features cannot depend each other in same layer Application Feature

    1 Logging Experiments Navigation Network SubFeature3 SubFeature2 SubFeature1 Architecture Dependency Injection
  12. Features cannot depend each other in same layer Application Feature

    2 Squeaks Network SubFeature3 Architecture Dependency Injection
  13. Features cannot depend each other in same layer Application Feature

    3 Squeaks Experiments Navigation Network SubFeature4 SubFeature5 Architecture Dependency Injection
  14. Features cannot depend each other in same layer How about

    sharing code - features between features then? • Every layer can have “common” package • Common can contains features, subfeatures • Features can be moved to common, if needed • But the goal to keep common features as less as possible
  15. Features cannot depend each other in same layer Application Feature

    1 Feature 2 Feature 3 Co-Feature 1 Co-Feature 2 Common
  16. core |-- common |-- moduleA |-- moduleB |-- moduleC |--

    moduleD features |-- common |-- moduleE |-- moduleF |-- moduleG |-- moduleH Features cannot depend each other in same layer ModuleH ✓ ✓ ✓ ✓ ✓ ✓ X X ModuleG ✓ ✓ ✓ ✓ ✓ ✓ X X ModuleF ✓ ✓ ✓ ✓ ✓ X X X ModuleE ✓ ✓ ✓ ✓ X ✓ X X ModuleD ✓ ✓ X X X X X X ModuleC ✓ ✓ X X X X X X ModuleB ✓ X X X X X X X ModuleA X ✓ X X X X X X ModuleA ModuleB ModuleC ModuleD ModuleE ModuleF ModuleG ModuleH
  17. Data / Domain / Presentation • Data ◦ Models ◦

    Network API ◦ Database API • Domain ◦ API integration ◦ Business logic / requirements ◦ Use cases, interactors, executors, trackers, etc. • Presentation ◦ Activity, Fragment, View, Presenting, Rendering, etc.
  18. Data / Domain / Presentation Why not use these as

    layers? • It is also common practice, although… • It does not provide flexible / scalable structure • It breaks the integrity of feature • It complicates finding feature and its dependencies
  19. Data / Domain / Presentation Feature based modules with Data

    / Domain / Presentation core |-- common |-- moduleA |-- moduleB |-- moduleC |-- moduleD core |-- common |-- moduleA |-- data |-- domain |-- presentation |-- moduleB |-- moduleC |-- moduleD |-- data |-- domain |-- presentation
  20. Gradle - Referencing • Use the buildSrc for defining consts

    ◦ Dependencies ◦ Plugins ◦ Scripts ◦ Modules • Reuse the constants in modules • Every module has the reference instead of hardcoded string • Easy to change from one place
  21. Gradle - Referencing object Plugins { val androidApplication = "com.android.application"

    val androidLibrary = "com.android.library" val googleServices = "com.google.gms.google-services" } object Scripts { private val gradleScriptPath = File("gradle-scripts").absolutePath val androidConfig = "$gradleScriptPath/config-android.gradle" val checkstyle = "$gradleScriptPath/checkstyle.gradle" val lint = "$gradleScriptPath/lint.gradle" val gradleCache = "$gradleScriptPath/build-cache-settings.gradle" }
  22. Gradle - Referencing object Modules { val networking = core("networking")

    val logging = coreCommon("logging") val feature1 = feature("feature1”) val feature2 = feature("feature2”) val commonFeature3 = featureCommon("feature3”) private fun core(moduleName: String) = ":core:$moduleName" private fun coreCommon(moduleName: String) = ":core:common:$moduleName" private fun feature(moduleName: String) = ":feature:$moduleName" private fun featureCommon(moduleName: String) = ":feature:common:$moduleName" } Please have a convention!
  23. Gradle - Scripts • Modularize your Gradle scripts as well

    ◦ Android ◦ Lint ◦ Detekt ◦ Findbugs ◦ etc. • Compose your “android-module” script • Compose your “android-java-module” script • ...
  24. Gradle - Scripts /** * This configuration is meant to

    be shared across all the modules so that * we can easily configure and modify each one of them from single point * without having the hassle of finding the usages in all modules. */ apply plugin: Plugins.androidLibrary apply from: Scripts.androidConfig apply from: Scripts.lint apply from: Scripts.findbugs apply from: Scripts.checkstyle apply from: Scripts.detekt apply plugin: Plugins.kotlinAndroid ➢ android-module.gradle
  25. Gradle Ideal gradle.build script would look like... apply from: Scripts.androidModule

    dependencies { implementation project(Modules.feature1) implementation Libraries.kotlinStdLib implementation SupportLibraries.design testImplementation TestLibraries.assertJCore androidTestImplementation TestLibraries.espressoCore }
  26. • How do you handle args? ◦ Especially the ones

    don't tokenize to string/query params • How do you check if a module has been installed with DFM? • How do you handle Transition Elements? • How do you handle the activity result? • How about type safety? Navigation Deeplinks are only destination, what you launch is still an intent.
  27. Navigation Thought for consideration • Using string format for deeplink

    with query params • Enable lint checks for formatting ◦ StringFormatInvalid ◦ StringFormatMatches <string name="deeplink_detail">detail?content-id=%1$s%26page=%2$d</string> String Integer val deeplinkPath = getString(R.string.deeplink_detail, contentId, pageCount)
  28. Navigation • Multiple Activities vs Single Activity? • Activities vs

    Fragments vs View? • In Application with Multiple Activities ◦ Mapping deeplink to activity is handled by system ◦ Internal schema with exported = false • Single Activity application requires you to implement mapping Deeplinks do not care :) As long as you map them to their destination
  29. Navigation So how would you implement? • Navigation module in

    core layer to have the functionality • Every feature defines its own Deeplink Parser / Resolver / Handler • Application binds all these together • Launch a deeplink to navigate to other module
  30. Navigation interface DeeplinkResolver { fun canResolve(uri: URI): Boolean fun resolve(uri:

    URI): NavigationRule } if(resolver.canResolve(uri)) { navigate(resolver.resolve(uri)) } fun navigate(rule: NavigationRule) { try { if(rule.resultCode != null) { startActivityForResult(rule.intent, rule.resultCode) } else { startActivity(rule.intent) } } catch (exception: ActivityNotFoundException) { handleError() } }
  31. Navigation Application Feature 1 Feature 2 Feature 3 Navigation Implement

    Functionality Provide Resolver Bind implementation with resolvers