11

I have an Android project which is architectured in a Modularized way. I have modularized the projects by dividing their source code between multiple Gradle modules, following the clean Architecture.

Here is the structure of the App.

enter image description here

The top module in this hierarchy, App is the one that no other module depends upon, is the main module of your application. The lower level modules domain and data do not depend on the App module, where the App module includes the data and domain modules. I have added the below code in the build.gradle of the app module

    implementation project(':domain')
    api project(':data')

Now, I'm having some issues with maintaining dependencies across each module. Since each of them is an individual android module, each of them having its own build.gradle. The App module can use classes in the data and domain modules. But, I have some general purpose classes, (Such as some annotations, Utilities, Broadcast classes, Dagger scopes etc) which I want to make use in all the modules. But these are the issues I'm facing

  • Since these classes are contained in the main module app, I cannot access these in my data and domain, because those modules do not depend on the higher layer app
  • Any libraries I'm using in all the layers (eg: RxJava) needs to be included in the build.gradle of each module

As a solution for this I thought of adding one more android module, say common which will be containing all my general purpose classes as well as the libraries which I use in all the modules.

All my other modules app, domain and data will be having this module as a dependency.

implementation project(':common')

So, any global libraries and classes will be added to this module and each of the individual modules will have only module-specific classes.

Is that a good approach? Or is there any way to solve this issue efficiently?

doe
  • 971
  • 2
  • 11
  • 27
  • demodularize your app? modularization only helps large teams work together efficiently.most apps don't really need it. if you're having trouble and are a single dev/small team, there is no point maintaining "clean-architecture" aside from a learning experience. is this just for learning or are you going to be putting this in production? – Karan Harsh Wardhan Jan 09 '19 at 06:33
  • @KaranHarshWardhan It's a production App where the size is bit large. So, we wanted to have some clear seperation across modules – doe Jan 09 '19 at 06:35
  • how does modularization help with the large size requirements? sounds like you're over-engineering – Karan Harsh Wardhan Jan 09 '19 at 06:48
  • @KaranHarshWardhan I wanted to give a try to [this](https://proandroiddev.com/intro-to-app-modularization-42411e4c421e). Forget whether its over engineering or not, but what would be an ideal solution for the above problems, if we ever go for such a modularization – doe Jan 09 '19 at 06:57

1 Answers1

17

We recently encountered this problem, as we transitioned to a multi-module project for reuse, build time optimisation (unchanged modules aren't recompiled), etc. Your core goal is to make your app module as small as possible, as it will be recompiled every time.

We used a few general principles, which may help you:

  • A common base-ui module contains the primary strings.xml, styles.xml etc.
  • Other front-end modules (profile, dashboard, etc) implement this base-ui module.
  • Libraries that will be used in all user-facing modules are included in base-ui, as an api instead of implementation.
  • Libraries that are only used in some modules are added as dependencies only in those modules.
  • The project makes extensive use of data syncing etc too, so there are also base-data, dashboard-data etc modules, following the same logic.
  • The dashboard feature module depends on dashboard-data.
  • The app module depends only on feature modules, dashboard, profile, etc.

I strongly suggest sketching out your module dependency flow beforehand, we ended up with ~15 or so modules, all strictly organised. In your case, you mentioned it's already quite a large app, so I imagine app needs feature modules pulled out of it, as does domain. Remember, small modules = less code to be recompiled!

We encountered some issues with making sure the same version (buildType, flavors) of the app was used across all submodules. Essentially, all submodules have to have the same flavors and buildTypes defined as the app module.

On the other side of the coin, multi module development does really make you think about dependencies, and enforces strict separation between features. You're likely to run into a few unexpected problems that you've never considered before. For example, something as simple as displaying the app's version suddenly complicates (disclaimer: my article).

This article also helped us decide on our approach. The article you linked also seems to be an excellent resource, I wish it had existed when we'd transitioned!

After comment discussion, here's an example diagram (with unfortunate untidiness, but enough to illustrate the concept. Note that distinguishing between api and implementation would be a good next step): module dependency diagram

Jake Lee
  • 7,549
  • 8
  • 45
  • 86
  • Thanks for the detailed explanation. Your approach sounds good. Also, I have some more queries on that. As per my understanding, the `base-ui` layer is the one which is closer to the framework. Does this layer act as a driver module for your dependency injection logic? We are using dagger2 for DI. So, the dependency resolution code, for all the sub modules, can exist in this layer? Which acts as a driver module for all other modules? – doe Jan 15 '19 at 09:03
  • One issue we faced durig the architecture was, We wanted to keep the interaction only between the releavant modules. For example, We need to have `app` -> `domain` -> `data` layer interation in such a way that, `app` is communicating to `domain` layer only, but not directly to the inner layer `data`. So, we are restricting access of any `data` layer classes inside `app` module, since `domain` layer is the one who is directly delaling with `data`. It is possible to restrict this access , if we add the `data` module to `domain` module as gradle `implementation`. – doe Jan 15 '19 at 09:04
  • But, since the top layer `app` (in your structure it can be `base-ui`) is the one who manages the DI logic, which means dagger exists in `app` layer and needs to have access to some of the classes in `data` layer to resolve the dependency graph. This forces us to add the `data` module either directly to `app` module, or as `api` to `domain` module, inorder to get access to `data` module. But adding it will allow access to any class in the `data` layer directly from `app` layer. Is that a good approach to do so? Or should the DI code exist in some other dedicated module? – doe Jan 15 '19 at 09:04
  • We didn't use DI, so I can't speak specifically. In general though, if you do go for smaller modules as mentioned, you avoid large portions of the app being able to access base modules they don't need. I think you're perhaps misunderstanding somewhat re: `app`, that is always the top layer. `base-ui` is the bottom. `dashboard` is in between. I could draw a diagram if it would help? – Jake Lee Jan 15 '19 at 09:11
  • **_I could draw a diagram if it would help_** It would be very helpful – doe Jan 15 '19 at 10:04
  • [Here](https://i.imgur.com/ZrEXZaS.png). It's a little outdated though, and I haven't distinguished between `api` and `implementation`. It should be enough to explain my thoughts though! – Jake Lee Jan 15 '19 at 10:24