Writing a modular project on Android

When we create a new project on Android Studio, it gives us one module, the app module. This is where a majority of us write our entire application. Each click on the run button fires a gradle build on our entire module and all the files in it are checked for changes. This is why gradle builds can take 10’s of minutes for larger applications and slow down developer output.

To solve this, complex applications like Uber decided to modularize their applications and have gained a lot from it. Following are some of the advantages of having a modular project

  • Faster gradle builds
  • Re-usability of common functionality across applications / modules
  • Easily pluggable into Instant apps
  • Better team work, as one person can have the sole responsibility of a module
  • Smoother git flows

Due to the above advantages, when I started off with the Posts application, I kept it in mind to use a modular approach from day one. The Android team has provided us with a few tools for it, but I did hit some roadblocks. Following are some of my learnings:

How do I split my modules?

Your application has sets of flows, for example, Google play has the application details flow, which contains the summary, description details, screen-shot and reviews activities.

All of these can fall under the same module — app-details.

Your application can contain multiple such flow modules like authentication, settings, on-boarding, etc. There are also modules which do not need a UI element to be present like — notifications,analytics, first-fetch etc. These modules contain the activities, viewmodels, repositories, entities and dependency injections pertaining to that flow.

Module structure for Posts

But there are always some common functionalities and utilities these modules would want to share. This is why you need a core module.

What is in the Core Module?

The core module is a simple library module in your project. The core module can, among other things,

How do I use 3rd party libraries?

One of the main responsibilities of the core module is to also provide external dependencies to your feature modules. This makes it easy to share the same version of a library among all your features. Just mark the dependencies with api in your core module and all your dependent feature modules would be able to receive them.

There is a possibility of a dependency only being useful in feature-a module but not in feature-b, both of which depend on core. In that case too, I would recommend to define your dependency in the core with api as proguard will take care of not including it into the feature-b instant app.

How do I use Room?

This one confused me for the longest time. We would want to define our database into the core module as it is a common functionality our application will want to share. For Room to work, you need a database file with all the entity classes mentioned into it.

But, as mentioned above, our entity classes are defined in the dependent feature modules and the core module cannot access them. This is where I hit a roadblock, and after some thought did the best thing you can do, ask Yigit for help.

Yigit clarified that you will have to create a new db file into each feature module and have a database per module.

This has some advantages:

  • Migrations are modularized
  • Instant apps contain only those tables they need
  • Queries will be faster

Cons:

  • Cross module data relations won’t be possible

Note: Do not forget to add the following dependencies into your feature modules for Room’s annotations to work

How do I use Dagger 2?

The same problem as Room was hit with Dagger too. My application class in the core module would not be able to access and initialize my feature module components. This is the perfect use-case for dependent components.

Your core component mentions the dependencies it wants to expose to the dependent components

Your module components define the CoreComponent as a dependency and use the passed on dependencies

Where do I initialize my components?

I created a singleton holder for all the components of my feature. This holder is used to create, maintain and destroy my component instances.

Note: Do not forget to add the following dependencies into your feature modules for Dagger’s annotation process to work

Conclusion

Although there are some tricky parts to convert your monolithic application into modules, some of which I tried to solve above, the advantages are profound. If you hit any roadblock with your modules, feel free to mention them below and we may work together on a solution.

Thank you!

Like what you read? Give karntrehan a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.