ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.

Follow publication

Modularization in Android: architecture point of view. From A to Z. Part I

Evgenii Matsiuk (Eugene Matsyuk)
ProAndroidDev
Published in
8 min readFeb 14, 2019

Modularization articles cycle:

  1. Modularization in Android: architecture point of view. From A to Z. Part I
  2. Modularization in Android: architecture point of view. From A to Z. Part II

Hello everyone!

Not so long ago we have realized that a mobile application is not just thin-client but it’s a huge diverse logic which we need to organize. That’s why we have been inspired by the ideas of Clean architecture, we understood DI and we learned how to use Dagger 2 correctly, and now we can split any feature into layers with closed eyes.

But the world is changing, and new challenges are coming out after old ones have been resolved. And one of the issues in Android world is modularity, or to be specific single modularity of the apps. Usually, you find out the problem when your app’s build time “flies into space”. And most of the articles and talks about Multi Modularity start with building time problems (references to Russian video 1 and video 2).
But many developers forget that single modularity affects not only build time but also app architecture. Now, let’s answer the following questions. How big your AppComponent is, how many lines of code does it have? Have you ever seen cases where feature A calls the Repository of feature B, but you feel that it’s not a good solution? Do those features have any concrete and accurate contract? And how do you build communication between features, i.e.are there any rules for that?
We think that we have resolved architectural problems by dividing the app into a few vertical layers (presentation, domain, and data, generally). Yes, dividing into vertical layers works, but is it enough? No, because at the horizontal level of our app there are no rules. And this task cannot be fixed only by dividing to packages or a more thorough code review.

And the final question to experienced developers. While transforming your app to multi modularity, were you forced to refactor half of your app, frequently dragging code from one module to another and having a non-compilable project from time to time?

In this article, I want to share my path to multi modularity specifically from an architectural point of view. What problems I had and how I tried to overcome them. In the end, you will get a ready algorithm for converting your single modular app to multimodular without tears and pain.

Answering the first question about how big my AppComponent is, it is really big, which made me sick. How come had it become so huge? The main reason was a bad organization of DI. So let’s start with DI.

How did I create DI before

I think, most of the developers “draw” the following picture on their mind when they think about building components and their scopes:

What we have

We have AppComponent that contains all dependencies with ‘Singleton’ scope. I am sure many projects have this component.

FeatureComponents. Each feature has its own scope and is a subcomponent of AppComponent or another parent feature.
Let’s focus on features. First of all, what is a Feature? In my understanding, a Feature is a logically full and independent module of a program that solves a particular problem, and it has predefined external dependencies and can be reused in other apps. Features can be big and small or even they could contain other features. If the Feature has an inner feature then external one could start inner one only by using strictly predefined external dependencies. For example, our app, Kaspersky Internet Security for Android, has such features as Antivirus, Antitheft and etc.

ScreenComponents. This is a component for a concrete screen that has its own scope. Also, it is a subcomponent from appropriate feature-component.

Now the list of “why’s”

Why Subcomponents?
The one thing I didn’t like in “component dependencies” was that a component can depend on several other components, and I think this could lead to chaos of components and their dependencies. When you have only “one to many” relationship (component and its subcomponents), it is much safer and clearer. Besides, the Subcomponent has access to all dependencies provided by the parent component, which is very convenient.

Why every feature should have its own scope?
Because I thought that every feature has its lifecycle that is different from other features. That’s why it was logical to create its own scope. Also, there is one factor to consider regarding multiple scopes, which I will cover later.

As we are talking about Dagger 2 with Clean architecture in mind, I want to mention how dependencies are delivered. Dependencies in Presenters, Interactors, Repositories are delivered via constructors. For tests then we can provide stubs and mocks to constructors, which will allow us to execute tests without any problems.
The closure of the dependencies graph usually occurs in Activities, fragments, or sometimes in receivers and services, in general in places where android can start anything. It is a common case when a developer creates a new activity for the feature. The Activity will have a feature component, and the feature will have three screens, which are implemented in three fragments.

Well, it looks like a logical case. But life makes its own adjustments.

Vital problems

Let’s look at a simple example from our application (Kaspersky Internet Security for Android). We have Scanner and Antitheft features. Both features will have a “Purchase” button. And “Purchase” is not only to send a network request but it is also a lot of different logic to handle different scenarios. It can be considered as a pure business logic with some dialogs. So here we have an independent Purchase feature. In the first two features (Scanner, Antitheft) we have to use the third feature (Purchase).
Let’s observe the UI and navigation of our example. In the beginning, the app starts the main screen where there are two buttons.

After clicking to any of the buttons, the app starts Scanner or Antitheft feature.
Let’s look at the Scanner feature:

Clicking “Start antivirus scanning” will initiate some scanning job, while “Buy me” will utilize Purchase feature, and finally “Help” will just open a simple help screen.
Antitheft feature is identical to Scanner.

Potential solutions

How do we have to implement this example from DI point of view? There are a few options.

Option #1

To make Purchase feature as an independent component which depends only on AppComponent.

But we get a problem here: how to inject dependencies from two separate DI graphs (components) to one class. We don’t have proper ways of doing this, only workarounds/hacks, which is bad.

Option #2

Purchase feature is created as a Subcomponent that depends on AppComponent. Scanner and Antitheft features will be Subcomponents of Purchase component.

But you know, we might have a quite number of similar cases in our applications. It means that the depth of the component dependencies may become really huge and complex. And such a DI graph will rather confuse you than making your app more beautiful and understandable.

Option #3

“Purchase” will become not a separate component but a separate dagger module. We can have two ways onwards.

First way

For all dependencies of Purchase feature, we set a “Singleton” scope, and add the module to AppComponent.

This is a popular option, however, it leads to AppComponent bloating. As a result, AppComponent becomes really big, contains all app classes, and Dagger’s essence will be in only providing dependencies to the classes (via fields or constructions, but not via Singletons). From one point of view, that’s the role of DI tool, but we are overlooking some of the architectural factors here and as a result, we get the case when everyone knows each other.
In general, when you are just starting, and you don’t know where to put some class (to which Feature), usually you make it Singleton. It’s a popular option when you work with legacy code and you try to introduce some architecture to the app, and you don’t know the codebase well yet. So these actions are justified at the beginning, however when the architecture of the app becomes more or less clear nobody wants to deal with AppComponent to refactor it.

Second way

It is having a single scope for all features, as an example, we can call it PerFeature.

Now we can add Purchase module to required components without any problems.
It looks comfortable at first, but our features are not isolated. Scanner and Antitheft features know everything about Purchase feature. There is a big possibility of misusing some of the classes. Purchase feature doesn’t have clear APIs, the border between features is vague, and there is no predefined contract. All of this might affect multi-modular architecture using gradle in a negative way.

Architectural pain

To be honest, I was using option #3’s first way for a long time. It was a necessary measure while we were transforming our app to have a better architecture. As I mentioned, when you choose this way your features start mixing and confusing with each other. Everyone may know about everyone, about their implementation details, and etc. And usually if your AppComponent starts bloating that means you need to do something.
By the way, option #3’s second way would help us to unload AppComponent. But knowledge about implementation details and mixing of features will still be there. Also, feature reusing between apps would not be an easy task.

Intermediate conclusions

So, what do we want to achieve? What problems do we want to solve? Let’s get through the points, starting from DI and moving on to the architecture:

  • Convenient DI mechanism that allows using features within other features (in our example, we want to use the Purchase feature within the Scanner and Anti-Theft), without hacks and pain.
  • Thin AppСomponent
  • Features should not know about other features implementations.
  • Features should not be available by default to anybody, we need to have fine-grained control over it.
  • A possibility of passing the feature to other apps with minimum efforts.
  • Logical transition to multi-modularity and best practices for this transition.

I intentionally mentioned multi-modularity only at the very end. We’ll cover it later on, let’s not get ahead yet.

Instead of Afterward

In the next part, we are going to consider how to fix the above-mentioned problems and how multi modularity will help us. It will be really cool!

Authors:
Eugene Matsyuk
Atabek Murtazaev
Roman Yatsina

Published in ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.

Written by Evgenii Matsiuk (Eugene Matsyuk)

Co-Founder at marathonlabs.io | Co-Author of Kaspresso | Android Google Developer Expert

Responses (4)

Write a response