Out of the box, Android Studio provides one module: the app module. Because of this, most developers write their entire application in this one module. This is fine for small teams and small applications. But, as an application grows, more team members are added and the application becomes more complex, build times can increase - with Gradle builds sometime taking up to 10-15 minutes - and developer productivity goes down.
One way to solve this problem for complex android applications is to modularize. Modularization means to divide a program into separate sub-programs (or modules) based on features. Each feature will be its own module.
Some advantages of modularization are:
- Ease of incremental builds and deliveries
- Smaller modules are easier to unit test
- Modules can be added, modified, or removed without any impact on another module
- Modules can be reused
- Reused modules can lead to smaller APKs
- Easily pluggable into Instant Apps
- Increased developer productivity, as one person can have sole responsibility of a module
How do we split up a monolithic application into modules?
For business critical applications, discarding the current application and rewriting it from scratch is not an option. Therefore, the only option for critical apps is an iterative approach where you figure out the features that can be separated from the main application and do it in an orderly sequence with the ultimate goal being a set of feature modules.
The key to splitting up a monolithic application into modules is understanding the dependencies between features. This is the software’s architecture. Knowing this will allow you to make decisions on which modules can be split apart and how much work that will entail.
Here are the steps to follow:
- Understand what will be shared. This might seem counterintuitive, but if we didn’t do this we end up duplicating large amounts of code. Many modules share resources such as activities, fragments, and layout files. Looking at the application’s architecture can help identify the utilities that will be shared across the various modules. Looking at the “as-is” architecture can even identify where these utilities have incorrect dependencies.
- Understand how to split what isn’t shared. Modules can end up with definitions of objects that are partial views of the real object. For instance, a shipping module may not need the order history of the customer but does need to know the address where a product will be shipped. By understanding the dependencies from the shipping code, we can understand the actual dependencies that code has on different parts of our domain objects. This can help determine the code needed to interoperate between modules.
- Understand how to split up the data. Splitting up a database means understanding the dependencies within the database. The first thing to think about is foreign key-primary key relationships between tables. But what about stored procedures that access parts of the database? Are there embedded triggers in one of the tables that have dependencies on another table within the database? Again, looking at the architecture of the database (all the dependencies) can help you plan how you will split up the database
- Manage the evolution. Refactoring is never done in isolation. The business still needs new functionality and bug fixes. You need to be able to both add new features and fix bugs at the same time. This is where incremental refactoring can be beneficial. Update a few pieces of the architecture and see how the application is affected by the changes.
There are many benefits to modularizing (refactoring) your Android application: improved maintenance, improved quality, faster builds, and improved developer efficiency. Modularizing your architecture does not have to be a risky, error prone process that requires a massive rewriting the entire application. Instead, it can be accomplished in an orderly and incremental way that minimizes risk while bringing the benefits of modularization.