Android Modularization: Hexagonal Architecture with Kotlin and MVVM — Part 1: The Big Picture
Not long ago, Android development was about “Activities” and messy structures. Codes in all types and shapes were written in one class. There was no clear structure whatsoever. In big projects, activity classes could contain thousands of lines of codes. Dark times!
But, like every topic in Computer Science, Android development had grown a lot through these years. Especially the introduction of Android Architecture Components, which was a huge step forward to a more pleasant development and more maintainable code for us developers.
Modularization of an Android applications is a new trend and frankly the hardest one to talk about. What I mean is, when AAC launched, there was a lot of documentation and sample codes all over the internet. For modular app though, not so much. The main struggle is everyone can have a modular architecture that is good for their specific requirements. This was our first problem. We couldn’t decide on one particular architecture.
The main goal of this article is to show you how to build a robust modular architecture, so I’m going to talk about how we converted our existing monolithic application to a modular architecture. If you want to know more about other stuff that is involved in this application, there are thousands of good sources out there. Also, Since I can’t share the actual project, I provided a sample project which is a simple library application, but it has the exact same structure as the main project.
Android Modularization: Hexagonal Architecture with Kotlin and MVVM — Part 1: The Big Picture (you’re here!)
You can't perform that action at this time. You signed in with another tab or window. You signed out in another tab or…
Keep in mind this level of modularity is time-consuming. It’s only useful if the app you’re making has a large code base and is hard to maintain. For small apps, this architecture is not recommended.
Android Modularization itself is not a new concept. And modularization, in general, is a well-practiced subject in programming. But not in Android (we can extend this sentence to IOS development as well). Before 2018, we heard only a few companies that modularized their applications (here is the article that Yelp wrote about the subject). So there are very few information on how to do it properly. Not even Google itself has a clear documentation on this subject. So before we dive into the details, let’s answer a basic question:
What are modules in Android?
Basically, we can use four kinds of modules in an Android application:
If you’re an Android developer, you’ve surely used this one. It’s used by “app” module, which is the entry point of the application.
apply plugin: 'com.android.application'
It’s similar to Application. If we want to access Android Framework references, such as Context, Activity, layouts, resources(like string and styles), ETC in a module, this should be the choice.
apply plugin: 'com.android.library'
These are the modules that only contain language references. No Android related stuff is allowed here.
apply plugin: 'kotlin'
New member of the crew. It’s basically Library Module with some fancy features! Such as on-demand delivery which is a huge space saver. In our example, all UI modules are using this type.
apply plugin: 'com.android.dynamic-feature'
Earlier I talked about using MVVM. But in the title, you read Hexagonal Architecture. So, how can these two work together? To answer this question, first, we should take a look at Hexagonal and understand how it works.
Through our research, we encountered an interesting architecture (thanks to our back-end team!) that goes by the name Hexagonal Architecture or Ports and Adapters.
What is it?
Hexagonal architecture was proposed by Alistair Cockburn in 2005. It’s a form of application architecture that promotes the separation of concerns through layers of responsibility. Yes, that sounds like every other architecture ever. So let’s get into the details.
How does it work?
The idea behind it is to put inputs and outputs at the edges of our design. In doing so, we isolate the central logic (the core) of our application from outside concerns. Having inputs and outputs at the edge means we can swap out their handlers without changing the core code.
Outside of the inner core, we have layers of ports (Interfaces) and adapters (Implementations) that capture messages from the outside world and convert them to appropriate procedures to be handled inside of the application. The resulting message from the application is then passed back through this layer of ports and adapters as an appropriate response.
In simple terms, each part has some interfaces, and if other modules want to work with them, they have to implement the ports that are provided. In a modular world, it means that no one cares what you do inside your module, as long as you have a proper response for me to use.
It’s a very simple yet powerful design. And remember it’s nothing but a bunch of Interfaces (port) and their implementations (adapter). Our goal is to implement this architecture into an Android application. For better understanding, take a look at these great articles:
Hexagonal Architecture: What Is It and How Does It Work? - NDepend
Hexagonal architecture is a model or pattern for designing software applications. The idea behind it is to put inputs…
Hexagonal Architecture: three principles and an implementation example
Documented in 2005 by Alistair Cockburn, Hexagonal Architecture is a software architecture that has many advantages and…
How to use it in an Android application?
Well, this the trickiest part. Since there is no actual implementation of this architecture in Android, we had to be the first. The start was smooth, but we had to change the way we implemented it three times to get the best result possible! Let’s get to the business.
The Basis of Our Modular Architecture
It took us three steps to come up with the best possible architecture:
We started with the basic separation of concerns. Each layer has one responsibility. Network module contains OkHttp and Retrofit classes (basically anything that is related to networking). It communicates with REST APIs. Persistence module is responsible for… well, persisting, with the help of Room database or Android’s shared preferences. Domain module is what connects these two modules together. It contains repository classes which UI modules can interact with.
As you can see, there is a straightforward structure here, and frankly, we weren’t happy with the result. Because it basically is a simple separation by package, but instead, it’s module.
At this point, we completely changed our structure. Now domain module has no reference to any module whatsoever. It merely is a connector between UI, Network, and Persistence.
See those tiny squares on top and bottom of the Domain module? Those are ports (Interfaces) which will be implemented (adapter) inside Network and Persistence modules. However, UI ports (which are the repositories) will be implemented by Domain itself. All the implementation of ports will be marked with
internal keyword. “internal” keyword makes a class invisible to other modules. Since we only use our ports to communicate between modules, it would be better to hide the implementations.
Example of port (Interface):
And corresponding adapter (implementation):
Now it looks like a proper Hexagonal Architecture, and we get to keep our MVVM design pattern between classes as well.
But we noticed a slightly annoying problem. Since Domain has no reference to Network and Persistence, it won’t be able to use their model classes. Network and Persistence should provide data using the Domain’s model. Well, someone might say just move all data classes to Domain, and that would be the right solution until you realize that Room needs its entity classes to be in the same module as itself.
On the other hand, Network classes had some variables we didn’t want our UI to know about them. Also, it would actually be useful if each module has its own data layer. So the solution we came up was using Data Transfer Object (DTO) between modules. By using this method, Network and Persistence get to have their own model classes, and when UI wants something from them, they have to convert classes to DTOs which are stored in Domain module, and UI modules have access to them.
There are several ways to exchange data between modules. We decided to go with manual conversion which I think is the best way (refer to this for more information):
fun PersonForm.toPersonRecord() = PersonRecord(
name = "$firstName $lastName",
age = age,
tel = tel
But as we continued using this method, we noticed some of the classes had too many variables, and this manual conversion might not have been the best idea. But other methods weren’t good either. Here’s an example of how some of our data classes looked like:
Do you see how those
fromDTO(from: TransactionHistoryDTO) functions make the code hard to watch? This is just one class, and we all know model classes can go really high in case of numbers.
Another problem we encountered was abstraction. We wanted to be able to swap any of those modules, but in this structure, swapping would be a pain. So we thought to take this architecture to the next level by abstracting the hell out of everything!
When people go to a bank, they don’t see (or care!) how the bank teller does all the work, or when they transfer money through an ATM, they don’t see exactly how that money is being transferred. The only thing they see and care is the result of their requested job. This is precisely how Abstraction Module operates in our example. UI plays the role of the customer in this analogy. When UI demands data, it calls a function from Abstraction module. UI doesn’t care how that function is implemented, it only cares about the result.
In this step, we added an Abstraction Module (in the actual project, the name of this module is bank module, and in the sample application, it’s library module). So what is the job of Abstraction Module? It contains repository ports (which will be implemented by Domain module) and DTOs, that’s it.
By using this architecture, UI only works with a module that is entirely abstract, and we can easily swap any module in the bottom half (or top half) of our structure.
The rule is, anything that wants to enter this module has to be abstract, and that means Interface and Abstract classes. But what about DTOs? They are neither Interface nor Abstract class. So we thought why not deal with DTOs the way we dealt with modules?
We converted all DTOs to Interfaces which contain only variables (some may have functions in them) and moved them to abstraction module. Thanks to Kotlin, it’s a great solution to our problems with DTOs. Take a look at this example:
We’ve declared a
User interface inside Abstraction module.
UserEntity, which is inside Database module, implements
User and its properties. Same goes with
UserResponse which is inside Network module. Now, for instance, if we need to fetch data from the database, the return type of the requested data will be in the form of
User interface. Since
UserEntity extends (implements to be exact)
User interface, returning result in that way is allowed. Same goes with
In this architecture, DTOs (for example
User ) are like ports and the implementation of them (for example
UserResponse ) are like the adapters. Also, by changing all of our DTOs to interfaces, we eliminated the ugly functions
fromDTO(from: User) from our classes. Kotlin automatically cast these objects to their parent’s type.
Now we have an abstract entity (Abstraction Module) with different functionalities (provided by interfaces) and properties (DTOs) which in our case is a Bank entity and has functionalities like transferring money and properties like deposit, credit card, ETC. It’s like we have a real Bank inside our application.
At this point, we have a complete Hexagonal Architecture that is applied to modules and MVVM design pattern is being used inside them.
In the next part, I’ll talk about the details of implementation using the provided sample project. It would be helpful if you take a look at the code beforehand.