Android & Kotlin, Multi Module( Layer Based)with Clean Architecture & MVVM+MVI Part 1

Süleyman Başaranoğlu
5 min readSep 4, 2023

--

Introduction

I’ve developed an open-source project that integrates a Multi-Module structure ( with Part I Layer-Based), Clean Architecture, and contemporary Architectural Patterns like MVVM and MVI. In this article, I will share the insights and methodologies behind combining these industry-relevant features.

Modul Graph & Flow ( 1.0 )

Modul Graph & Flow

In the top image, we can see the Multi-Modules categorized as Data, Domain, Presentation, and Core&Common. For this version, I opted for a Layer-Based modular division. In the next Version 2, I plan to adapt a Feature-Based approach for this app. For your own projects about Feature-Based approach, it’s can be possible to adopt a Feature-Based structure as simply depicted in the graph below.

Feature-Based Graph ( 2.0 )

DATA MODULE

Let me try to elucidate the structure a bit. On the right side(1.0 Image) , we have data coming from the backend in JSON format. This data is stored in data classes within the Data Module. If desired, you can annotate the fields using Serialization.

Data Response

API

The API is structured as an Interface, and the reason for this is in larger projects, for each API Endpoint, especially in the Feature-Based approach I mentioned above, we can have a separate Interface. This approach ensures the code remains Clean Code, adheres to the Single Responsibility and Single Source of Truth principle. Here, you can see that we make HTTP requests using Retrofit.

Data API

Data Source

The Data Source is where we keep track of whether our data comes from the Backend, Web, or Local sources. It can be termed as Local and Remote, depending on the requirement.

The reason for the implementation being an Internal class is that I intend to use it only within this module. It’s being an interface will facilitate when writing Unit Tests, especially with mockk, for our Data Source.

Data Source

Repository

Now, let’s talk about the Repository. There are various approaches and perspectives available in numerous articles, writings, and books. These often revolve around whether the Mapper should be in the Repository and its respective duties. In my project, I’ve designed the Repository to serve solely as a bridge. It not holds Business Logic, Mapper etc. The primary role of the Repository is to specify the source of the data in the project, be it Remote or Local. In other words, it determines if the data is coming locally from sources like Shared Preferences or databases like Room, SQLite, or remotely from APIs, Web Services, etc.

Repository

handleAPICall extension : This structure is utilized to handle the results of API calls in a more organized and comprehensible manner. If the data from the API is a Success, it then operates accordingly using interfaces and the Retrofit Response.

Use Case Implementation

Lastly, let’s delve into the Use Case Implementation in the Data Module. Fundamentally, a Use Case takes in the Repository and Mapper, and operates asynchronously using the suspend functions we’ve written so far. It determines the runtime environment of the threads using Dispatchers. Based on these, it orchestrates the flow of data. For instance, when implementing pagination in the code block, it holds a certain logic to determine which endpoint to request while retrieving information from the repository.

Use Case Implementation

If you’ve noticed, from the API onwards, we’ve consistently used Suspend. The reason for this, is that I don’t want to block the Main Thread during API operations. For this reason, in the Use Case, I annotate with the IO dispatcher, aiming to ensure these operations are asynchronous and switch to the IO thread with the help of the dispatcher.

Mapper : Input is Data Module Model, Output is Domain Module Model. Bridge to Data Module & Domain Module.

DOMAIN MODULE

In the Domain Module, there should be no dependencies on any other layer. It encompasses the core business rules, data validations, computations, and other foundational functionalities of an application. Due to its significance, any modifications within this layer can ripple changes across other layers. To avoid unintended side-effects, it’s imperative to keep it decoupled. When you change elements in the Domain Module, it can often necessitate changes elsewhere. Therefore, adjusting other components shouldn’t obligate modifications in this module.

Use Case. Domain

COMMON MODULE

The Common Core Module objects, classes, fields, utilities, custom views, extensions, and the like that are used universally across the application. I place the Model Interfaces here because they are shared among different components. By centralizing common processes and functionalities within this module, we can significantly reduce dependencies and avoid code duplication. And Common&Core has not dependencies the other modules.

Common&Core Module

Our final module is the Presenter Module and we’ll see Compose in the next Part. We’ll continue discussing next partwhere we’ll delve into working with both MVVM and MVI Design Patterns and Feature Based Modulerization. Additionally, we’ll touch upon widely-used technologies in the industry, such as Compose, Navigation on Compose, Performance tricks. And after Pagination, Shimmer, Multi View Type Adapter, and both Unit and UI Testing and more technologies we’ll talk.

Your feedback is of utmost importance to us. Whether positive or negative, pointing out areas for improvement or suggesting changes would be greatly appreciated. Thank you for taking the time to read. For more and open-source code -> https://github.com/basaransuleyman/Multi-Module-Clean-Architecture-Android-Kotlin

For Jetpack Compose Fully Projects

Jetpack Compose with Hexagonal and Clean Architecture

Boost Your Android App with CPU Profiling

Boost Your Android App with and Mastering with Memory Profiling

Enhancing Code Quality Lint with Custom Rules

Jetpack Compose Bad and Best Practices

--

--