A Cleaner way to Modular Architecture in Android

Mohammed Miran
NE Digital
Published in
5 min readJan 6, 2021

--

What is a Modular Architecture in Android world?

Simply put, in a modular architecture the app is divided into multiple gradle modules instead of one huge “app” monolith.

This article does not focus much on understanding the Modular Architecture. Instead it will try to solve some of the very commonly faced problems while building an app which follows this architecture.

Why Modularise?

  1. Easy to maintain — An organisation(like ours) having quite a number of business verticals would definitely want each vertical to be maintained individually as a separate module. In fact, at NEDigital we have separate repositories for some of the Modules.
  2. Clear separation of concerns — We would not want a code change in a feature to cause any side effect in some other feature.
  3. Plug & Play features — A feature is not performing well, or is not applicable for a Region? Simply deactivate the gradle module from the build script.
  4. Faster build speeds — The entire app is not built when we modify a piece of code within a module, instead only the module(and its dependencies) that’s been modified is built.

We can further improve the build speed in dev environment by not including the modules in the build script that are not relevant. We will see how this is done in another article(please follow NEDigital Space).

Common problems faced while developing in the Modular World

Each section below will describe a problem commonly faced by organisations adopting this architecture. Later, we will introduce a framework, what we call it as Module Initialiser Framework(MIF). We then try to solve each of these below described problems using this framework.

How do we initialise Modules?

An application can contain a lot of Modules. It can also contain a bunch of common modules that other Modules depend on. A typical way to initialise a Module is by calling an init() method on a Module. This init() method is usually called from the Application class which is residing in the app module.

Problem 1 App Module needs to be aware of all the Modules present in the entire application in order to do their initialisation.

Order or Sequence of Module Initialisation

How do we make sure a common/core module is initialised first before its dependencies? Well, again, a typical way is that the app module while doing the initialisation will have to make sure that order is correct. Now, we are leaking this logic into app module which defeats the entire purpose of Modularising the app.

Problem 2 — App Module needs to be aware of the order in which Modules are to be initialised

Accessing ApplicationContext statically

It is a common practice in Android to store the ApplicationContext inside the Application class and access it from anywhere statically. But, in Modular Architecture, since a feature module does not depend on app Module, accessing the stored ApplicationContext instance is not possible.

Problem 3 — No easy way to statically access ApplicationContext

Dependency Injection — Providing Dagger dependencies to other Modules

Dependency injection using Dagger is itself a complicated subject. Adding Modular Architecture on top of it will make things even more complicated.

Problem 4 — Dependency Injection; How do we provide dagger dependencies from one Module to another(Core Module to a Feature Module)?

Module Initialiser Framework, aka MIF to the rescue

An activity has an onCreate method which the platform calls when the Activity is created. Similarly we also have Application level onCreate method. What if I say we can have a Module level onCreate as well? Well, this is what we are going to achieve in this article.

A Module is actually a gradle feature and it has got nothing to do with Android. In other words, unlike Activity, Service or a ContentProvider, a Module is not a component in Android. So, we at NEDigital, built this framework which can help initialise, provide and maintain our Modules in a clean and more importantly in an ‘Android way’.

The MIF will provide a way to create and initialise a module just like an Activity or an Application.

Steps to use the MIF

Let me first demonstrate how we can create/initialise a Module using the MIF. We will then later discuss the internals of MIF and its source code.

Step 1: Create a library module by following standard Android Studio steps, call it FeatureOneModule

Step 2: Create a class which extends MIFModule. MIFModule is the base class for all our modules. Then implement onCreateModule() and dependencies() methods.

onCreateModule() — provides the application instance and also a bundle. This application instance can be saved and accessed statically if required(Solves the problem 3). We will see later where this bundle come from and how we can use it.

dependencies() — Should return a list of other modules this module depends on. MIF will ensure that those modules are initialised before the current module. E.g., FeatureOne module would need core module, so return CoreModule in the list of dependencies(solves the problem 2).

Step 3: Register this module in Manifest(just like how you register an Activity or an Application)

That’s it! You have a new Module created.

Note the meta-data here. This is passed as a bundle to the onCreateModule method. This bundle may contain configurations or pretty much anything you wish to pass to your Module.

Take a note on the intent-filter. This is mandatory and this is what makes your Module “discoverable”.

Lastly, make sure your Application class extends MIFApplication.

Get the Instance of a Module

We would want to retrieve the actual instance of a Module. It can be our current Module or some other module the current module depends on. E.g., FeatureModule may depend on CoreModule. This is how we can do it.

Nitty-Gritty of the MIF

As discussed earlier MIFModule is the base class for all our Modules. What I did not tell earlier is that MIFModule is nothing but an AndroidService. So, the ModulesManager will “discoverall the services aka Modules which are of type com.yourpackage.mif.MODULE_INTENT and then initialises them.

ModulesManager — is responsible for,

  1. Discovering all the Modules in the app.
  2. Initialising dependent modules first.
  3. Then initialise the actual module.

Source code

Thanks for reading the article. Do follow this space to receive updates on follow-up articles on this topic. I will soon be posting an article covering various case studies which uses MIF.

--

--