The android clean architecture [POV]

Hello fellas! It’s been a while since I wrote my last article (it’s almost 3 years) #lol

Yeah, it’s not your fault… just Me, have been so busy during my daily activity as an Engineering Manager at one of the B2B eComms in Indonesia (I know it, you’ll notice that later) #lmao

Anyway, let’s talk about android development (again). Maybe this is one of the most (still) hot topics within Android Developer’s life. I think everybody already learned this things, because nowaday this is the most important part for anyone who will try to learn android development

Today, I would like to talk about The clean architecture for Android Development from My Point of View… without any further do, let’s begin with the intro~

As of mentioning in Clean Coder Blog written by Robert C. Martin a.k.a Uncle Bob. Basically the terms of a clean architecture is…

To think about how to make a separation of concerns that could be match into a SOLID principles instead

Clean Architecture Diagram by Robert C. Martin

The goals of these architecture should be;

  • Independent of Frameworks
  • Testable
  • Independent of UI
  • Independent of any Datasource
  • Independent of any external agency

The dependency rule means that source code dependencies can only point inwards. Nothing in an inner circle can know anything at all about something in an outer circle. In particular, the name of something declared in an outer circle must not be mentioned by the code in the an inner circle. That includes, functions, classes. variables, or any other named software entity…

Entities of a clean architecture are used to encapsulate Enterprise wide business rules. An entity can be an object with methods, or it can be a set of data structures and functions. It doesn’t matter so long as the entities could be used by many different applications in the enterprise. If you don’t have an enterprise, and are just writing a single application, then these entities are the business objects of the application…

Use cases are contains application specific business rules. It encapsulates and implements all of the use cases of the system. These use cases orchestrate the flow of data to and from the entities, and direct those entities to use their enterprise wide business rules to achieve the goals of the use case. We do not expect changes in this layer to affect the entities. We also do not expect this layer to be affected by changes to externalities such as the datasource, the UI, or any of the common frameworks. This layer is isolated from such concerns…

Conforming to these simple rules is not hard, and will save you a lot of headaches going forward. By separating the software into layers, and conforming to The Dependency Rule, you will create a system that is intrinsically testable, with all the benefits that implies. When any of the external parts of the system become obsolete, like the database, or the web framework, you can replace those obsolete elements with a minimum of fuss

So, how about these explanation of the clean architecture from their founding father ? Did you got a point of what you need ? In order to implement in your android code ?

It is ok if you are stil confusing about it… so do I #lmao. Let me explain about this things from my point of view instead…

Android Clean Architecture from My Point of View (POV)

The following above diagram are based on my point of view on how to translate the definition of a clean architecture itself, into the android development lifecycle. Let me explain more detail instead.

This layer are used to store and processing datasource, either from a local database (using Room/SQLite) or from a network data processing through backend API (by REST/GQL). Each data processing inside the layer could be wrap into a datasource factory. For example;

  • We have a following network datasource for user login like this;
interface UserNetworkDatasource {  @POST("v1/login")
fun login(@Body request: LoginRequest): Single<LoginResponse>
}
  • Then, somehow you (also) have a local datasource to store user login information into your local database, by using Android Room DAO with a UserEntity object like this;
@Dao
interface UserLocalDatasource {
@Insert
fun insert(user: UserEntity): Completable
}
  • You can combine both request from network and local datasource into a datasource factory like the following example;
class UserDataFactory(
private val localDatasource: UserLocalDatasource,
private val networkDatasource: UserNetworkDatasource) {
fun login(request: LoginRequest): Single<LoginResponse> =
networkDatasource.login(request).doOnSuccess { response ->
localDatasource.insert(
UserEntity(
token = response.data.token,
refreshToken = response.data.refreshToken
)
)
}
}
  • Or if you do not want to store any result from API, you just create a factory like this;
class UserDataFactory(private val networkDatasource:    
UserNetworkDatasource) {
fun login(request: LoginRequest): Single<LoginResponse> =
networkDatasource.login(request)
}

Business logic and purely programming function, without android specific function and module, should be represented on this layer instead. We can achieve any business logic based on a domain of the business itself, on this domain layer. Without knowing which UI that will be used in a presentation layer.

This domain includes a repository pattern that could be used to call the datasource factory and also for the usecase or interactor based on the business logic functionality, that the application has. Along with business entity or params with a pure Kotlin/Java code like the following example;

  • The repository pattern are used as an interface between data and domain layer. You can achieve those implementation like the following example;
interface UserRepository {  fun login(params: LoginParam): Single<UserEntity>
}
class UserRepositoryImpl(private val userDataFactory: UserDataFactory) {fun login(params: LoginParam): Single<UserEntity> =
userDataFactory.login(LoginRequest(
email = params.email,
password = params.password
)).map { UserEntity(
token = it.data.token,
refreshToken = it.data.refreshToken
)
}
}
  • Usecase or interactor of a business logic are used as a representation of a usecase in your application feature. This usecase should not depend to any UI, it just depend on your current business logic, for example we have a login usecase implementation like this;
class LoginUsecase(private val userRepository: UserRepository) : 
Usecase<UserEntity, LoginParam>() {
override fun buildUsecaseObservable(params: LoginParam):
Single<UserEntity> = userRepository.login(params)
}
  • Entity of the business logic, should be represented per feature or functionality like the following example and it only contains language feature (Android feature module, such as parcelable should not be here) by default;
data class UserEntity(
val token: String,
val refreshToken: String
)

Those entity implementation also applies for the business logic parameters;

data class LoginParam(
val email: String,
val password: String
)

How about the presentation layer implementation ? should we use just only one implementation ? like most of articles out there, that always relates the clean architecture implementation on top of Android Model-View-Presenter design pattern instead of using any other android design pattern

The answer absolutely NO!!! #lol

You can always achieve any android design patterns, inside your clean architecture implementation. You can use following implementation of android design patterns;

  • Model-View-Presenter (MVP)
  • Model-View-ViewModel (MVVM)
  • Model-View-Intent (MVI)
  • Model-View-ReactiveProgramming (MVRX)
  • Other MV* (whatever) #lol

Also, you can combine both MVP on a specific feature and other feature, that is using the android MVVM for example

Because your presentation layer is always based on your feature/business needs. You cannot forced some feature to always use MVP/MVVM/MVI/MV*, if it cannot solved your business problem or your usecase things

Let your team decide what needs to be done on your current presentation layer, and let the domain layer and data layer work separately as expected. Thus, your implementation always follow the single source of truth principle

You can always test your data layer, domain layer and also presentation layer on a different test cases, then you can always update or change it based on your needs and also based on your problem on each layers

From my point of view, there are several project structure implementation out there, that already implemented by using the android clean architecture approach

For example, you can achieve your implementation of a data, domain and presentation layer, on a separate android module like diagram below;

Project Module Based on Clean Architecture Layer

The diagram above tells us that the android feature module are created based on a clean architecture layer, which each of the layers are represent as an android module, inside your application

There is still a benefit and disadvantage of using this approach, such as;

  • Your android application module are based on clean architecture layer
  • You can modify each module per layer based on your business needs
  • You have to maintain all layer on each module, if the business are change their requirements, and it sounds not good for you #lol

On the other hand, there are another approach that (I think) could be match with anyone that want to implement an android clean architecture but also need to maintain per feature module inside their application

Project Module per Feature with Clean Architecture Layer Inside an Android Package

The benefit and disadvantage of using this approach are;

  • Your feature module is (absolutely) your android module, that based on your business needs
  • You can toggle and/or add/remove every time of your module, if your business does not need one of the feature in the future
  • Manage your clean architecture based on your feature module, makes you have to implement each layer inside your feature module that will be made your android project package structure become huge per each module #lmao

The android clean architecture implementation could make your code are clean inside your business application, also your code will be testable easily than before. It is because you can make any test based on your module per each layer, in a clean architecture layers

You can isolate your business logic, data structure with a single responsibilities per each module, without disrupt your UI implementation, then you can implement any of android design patterns on your presentation layer without making any changes on your current business logic/requirements, every time you change your design patterns

Your team can easily manage their own design patterns on each presentation layer based on their needs and problems that they faced, without having much changes in a business logic inside your application modules

So, they can minimize their house keeping/refactoring code task, which can spend so much time and energy for your team in the future, then they can focus on your application feature implementation instead

The only way to do great work is to love what you do