Android Modularization: Hexagonal Architecture with Kotlin and MVVM — Part 2: The Connections

Peyman Sepehrad
5 min readAug 21, 2019

--

Photo by Omar G. Garnica on Unsplash

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:

build.gradle file from Network module

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:

template-android-library.gradle

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:

KoinModules object inside Database module

And here’s how “app” module gathers them all:

ModuleProvider object inside “app” module

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:

Koin modules inside a UI module

The declaration of the EntryPointActivity :

EntryPointActivity class

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:

IntentLoader

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:

Starting HomeActivity form outside of the module

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.

--

--