Android Modularization: Hexagonal Architecture with Kotlin and MVVM — Part 2: The Connections
In this part of the article, I’m going the talk about how to manage the connections between modules by using the power of Koin, Gradle, and Kotlin. And also talk about navigation between activities in a modular architecture.
Android Modularization: Hexagonal Architecture with Kotlin and MVVM — Part 1: The Big Picture
Android Modularization: Hexagonal Architecture with Kotlin and MVVM — Part 2: The Connections (you’re here!)
The Players
Before we dive into the details, I’d like to introduce all the different things we used in this project.
Kotlin
The biggest change for us developers since we got into writing code for android devices is Kotlin. We’ve been using it for couple of projects and felt really good about it. So we decided to give it a go for this one too.
Coroutines
Finally something that can handle Android’s background operations the best way possible. This one really changed the structure of our code.
Koin
Another exiting announcement (at least for me) was Koin. We’ve been waiting for a good replacement for Dagger. While dagger was really hard to implement in modular world, The simplicity of Koin helped a lot during this process.
OkHTTP and Retrofit
When it comes to networking, these libraries seem to be the obvious choice. Also the latest version of Retrofit has native support for coroutines which is great.
MVVM with LiveData, ViewModel and RoomDB
And last but not least, we used the famous trio of Android for persisting and transferring data between classes.
That’s right, No more ReactiveX! By combining all of the things i mentioned, we finally managed to terminate RxJava/RxKotlin from our code base!
Gradle Dependencies
First of all, let’s talk about how modules are connected in this application. Hers’s a reminder of how modules depend on each other:
Kotlin in Gradle
No, we didn’t use Kotlin DSL (I’ll tell you why) but we used Gradle Dependency Management (GDM) with the help of buildsrc
module. What is it? It’s the practice of keeping your dependencies centralized and under control with Kotlin. Here’s an example:
And all the libraries and their corresponding versions are stored in a Kotlin object
inside the buildsrc
module. If you want a step by step guide to implement this, check out this great article.
Gradle’s “apply from”
In the above snippet, the first line of code is apply from: “$rootDir/${GradleTemplates.androidLibrary}”
. This is basically a template which we can apply to any .gradle
file and avoid writing boilerplate code. this is a very powerful tool when we’re dealing with a lot of modules. Here’s the androidLibrary
template we used for the above snippet:
Earlier I said that we didn’t use Kotlin DSL instead of groovy. we wanted to, but turns out Kotlin DSL doesn’t support apply from:...
yet. Bummer!
Koin’s Effect
When we decided to modularize our application, the only option we had was google’s Dagger, and it was a nightmare to implement it in a modular world. So we postponed modularizing and waited until something as good and complete as Dagger came up. The release of Koin was a great news for us, because it’s probably one of the most powerful DI libraries we’ve seen and it’s unbelievably simple to use.
Implementation
In this architecture, each module have to provide its Koin modules itself. And then the “app” module (entry point of application) gathers all the Module
classes (Koin’s MoudelDeclaration
class) from each module (Network, Domain, ETC) and starts the Koin from Application
class. Here’s an example:
And here’s how “app” module gathers them all:
And then starting the application:
Problems with dynamic modules
As I mentioned in the previous part, all of our UI modules are “Dynamic Feature” module. We wanted to do the same with all the UI modules but it doesn’t work like that. See, “app” module does not have access to dynamic feature modules, which is weird considering the “app” is the connector of all modules. So we came up with an idea. In our architecture, every UI module has an “Entry Activity” and if user wants to navigate from one UI module to anther, The “Navigation” module (I’ll talk about this later) starts this activity. So, each UI module has its own entry point which is an Activity.
The solution
The solution we came up was having an EntryActivity
class (which extends AppCompatActivity
) and load modules inside it. All the entry activities in UI modules must extend this class. Here’s how it works:
The declaration of the EntryPointActivity
:
And the usage:
This is basically all the work it takes to setup Koin in a modular application. It’s so simple that we couldn’t believe the first time we successfully build the app and got no error!
Navigation
In a monolithic architecture, navigation between activities is easy. Since we have access to all classes inside a module, we can easily start any activity from anywhere. But in a modular world, there comes a time when we need to navigate to an activity which we don’t have access to. To solve this issue, we created a Navigation Module.
How does it work?
First of all, we create an IntentLoader
to fetch intent of any Activity:
Then, for each module we create an object from which we can start any activity inside that module. But remember the activity we want to start from an outside module has to extend EntryPointActivity
. For example, if we want to start HomeActivity
:
Before I forget, the above class implements DynamicFeature<T>
which is just a simple generic interface to ease up the work:
interface DynamicFeature<T> {
val dynamicStart: T?
}
And then simply start the activity from anywhere:
We can also start fragments using this method. Just create a fragment loader and we are good to go:
internal fun String.loadFragmentOrNull(): Fragment? =
try {
this.loadClassOrNull<Fragment>()?.newInstance()
} catch (e: ClassNotFoundException) {
null
}
We know that this is not exactly the best solution to navigate, but until something better comes up, this is our best choice.
So, this was all about the ways to connect modules together. Hope you enjoyed it.