Think of current Android Architecture from UseCase

“No silver bullet.” I think everyone has heard this sentence more or less. It tells us “We can’t find an universal method to solve all problems.”
As wo all know, we have many architecture designs, but we can’t use one of them and use it to design all of our projects.
As new architecture designs are constantly emerging, from chaos to MVC, MVP, MVVM and MVI.Architecture design is also evolving in continuous integration. We are also trying to find a way can solve our current problems.
Architecture is a compromise of objective inadequacy, and specification is a compromise of subjective inadequacy.

My architectural design history

With the developing, maybe all of us used many kind of architecture design, and we also have our own understanding of architecture design. For me, I have several stages of understanding of architecture design.

Fist of all, Nothing is here.

This stage is roughly from I start to learn in primary school(is’ you logo ), until my university.
This stage is not have any architecture design. I just write code as I want.
Then I think it’s not necessary to have any architecture design at this time, it’s just a pure pragmatism.

It’s also in line with the development of things, such as the golden age of Moore’s Law in the past few years.
But in this years, we can’t say a new Moore’s Law or more.

Then the architecture design is also naturally appeared. With the development of the software, maybe is an different problem, or the performance problem, or others.

Even though I only write hello world, after writing it 1k times, we also to think how to write faster and batter.(Ok maybe only 3 or 5 times)

Although this stage we will encounter various problems, or try to do something that can be regarded as the embryonic form of architectural design. Even if it is very rudimentary.

But no matter what, I haven’t any concept of software architecture at this stage.

MVC Open the sky, is it?

Until one day, I heard the MVC in the course, for my with empty of architecture, it’s door in new world.(I have write for(i < 10){sleep(1000)})

Today the MVC design pattern maybe is not very suitable for Android development, but for me, it’s a process of creating a transaction from scratch.

My first though is: ‘I can do this?’
Even if it’s not a very suitable, and I added many problems by myself.
I used MVC in a “big” project at that time.

Then I found, it is really useful.
It is much more practical and has more readability than my previous barbaric growth code.

And I also have a new understanding of the architecture design.

MVP rise, don’t rise!

Until my first year of work, I also think “use new, not old”.

But when I started a new project.
This project’s architecture is MVP, the first time I contact it, I’m curious about new technology, and I also think new technology must be better than old technology.

But, our project aren’t fix MVP.
In MVP design, P layer need to handle a lot of View information passively, and they also need to deal with the data synchronization between View and Model.
Our project is a information flow display project, and it’s more about the View action, with the continuous change of function, it often leads to a lots of changes in the P layer.
At this time, I will think, maybe I can write this part in Activity.

So, the new architecture design is not completely suitable for the new project?

MVVM appear, invincible under heaven

Being with the evolution of MVC and MVP, MVVM also appeared.

In this time, many project use MVVM as the main architecture design.
It all is Google recommendation Android architecture design.https://github.com/android/nowinandroid
(I will explain it in detail later.)

When developing a new project, the first design is MVVM.

So, MVVM is silver bullet?

Jetpack emerge, enemy from heaven

When we use Android Jetpack Compose developing Android, we found MVVM is not suitable for it.

One of it is that many kotlin new features(such as coroutines) can not be perfect integrated into MVVM, and another is that many library’s support, lead to many MVVM’s convention method(such as DataBinding) can not be used in Jetpack Compose.

what is the next step?

At this time, the radical said, we need try a new architecture design to adapt new development. MVI mode is coming, maybe many develop are not heard about this architecture. Because of it is base on Jetpack Compose architecture, and it is too radical, try import new design(Intent/Action) to solve the event trigger between different layer, it make learning cost is higher than other’s.

And the conservative said, you radical are too conservative. We need a new architecture design’s design. Or a new common method to design architecture.

And now , UseCase is coming, with Unidirectional Data Flow(UDF) design.

Unidirectional Data Flow(UDF)

This is Android recent recommended for Android architecture: https://developer.android.com/topic/architecture?#modern-app-architecture

This is just a part of all, except this, there are also Separation of concerns, Drive UI from data models and Single source of truth(SSOT).

In UDF the state is only flowing in one direction. The events that modify the data flow in the opposite direction.
It should be noticed, UDF often need to use with SSOT.

Before we talk about MVI, the UDF maybe is a high completion of MVI, and when MVVm is split the responsibility, it also is comply with this principle.

So I still think the MVVM until is recommendations for Android architecture.

Then, let’s to see what is recommendations in Google.

More detail info you can view in Google official platform. In short to say, It is UI Layer, optional Domain Layer and Data Layer.

Then we can see the difference between MVVM and Google’s recommended Android architecture.

In fact, any part of MVVM model can be found in the new architecture.

Find the cause, maybe “More and more developers is bad on ViewModel”. In traditional MVVM design, we added too many function and rule in ViewModel and make it more and more bloated.
When we find this problem, we can’t split it to a suitable result.

Then we analysis each layer’s content and solved problems.

UI Layer

Let us to see some picture from Google.

We think UI = UI element + UI State. So UI Layer contains View/Compseable and ViewModel, we split original ViewModel to UI State and Logic ViewModel.

In UDF and SSOT, we can see the data is only flow in one direction at UI Layer.

Compare with MVVM, this layer is not only View, but also ViewModel’s UI Logic.

Data Layer

Compare with MVVM, this layer is similar to Model in MVVM, it’s provide data and logic to upper layer.

Domain Layer

This layer is responsible for encapsulating complex business logic, or simple business logic that is reused by multiple ViewModels. So, not at all App need this layer.

This new setting is actually more towards the business logic part in the ViewModel of MVVM.

This part, often used by UseCase to implement.

UseCase

Simple to say, Domain Layer’s UseCase is the part of our code logic, So why we need to design UseCase, or what is the feature of UseCase?

  1. Not hold state
    In order to ensure UDF and SSOT, we don’t want to hold more state in logic, we hope it can work like a pure function.

  2. Single Responsibility Principle
    One UseCase only do on things, avoid the file’s content too many info.(Instead of it, we have more file)

  3. Optional
    Although Data Layer is a optional Model, UseCase is also a optional part. Because some simple business logic can be used by UI, but the new question is, if it is optional, we will not use it at last.

Though a thousand words, it’s better to understand it after actual used. Let’s use a simple code to explain how to use UDF for a project.

Code Demo

This methods is simple, user can click Yao to change Gua, and show some base Gua explain.(Yao is 爻, Gua is 卦, this is Chinese traditional culture.)

The code directory structure is roughly as follows.

Then we will check each layer’s content.

UI Layer

This have 2 UiState(GuaBaseUiState, GuaExplainUiState) reasons is that the page have 2 part, one is sync content update, another is async(like network) content update.

The first of part is about ViewModel and Composeable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@HiltViewModel
class GuaViewModel @Inject constructor(
// model layer
private val defaultDatabaseRepository: DefaultDatabaseRepository,
// domain layer' UseCase
getGuaExplainUseCase: GetGuaExplainUseCase,
getGuaBaseUseCase: GetGuaBaseUseCase,
private val savedStateHandle: SavedStateHandle,
) : ViewModel() {
private val searchQuery = savedStateHandle.getStateFlow(
key = SEARCH_QUERY,
initialValue = SEARCH_MIN_FTS_ENTITY_COUNT
)
private val currentYao = savedStateHandle.getStateFlow(
key = YaoS,
initialValue = YaoS_Default
)

// UI State update
val getYaoUIState: StateFlow<GuaBaseUiState> =
currentYao.flatMapLatest { query ->
......
}

val getGuaExplainUiState: StateFlow<GuaExplainUiState> =
searchQuery.flatMapLatest { query ->
......
}

// trigger UseCase
fun initDatabase(){
defaultDatabaseRepository.guaTableInit()
}

fun onSearchQueryChanged(query: Int) {
savedStateHandle[SEARCH_QUERY] = query
}

fun onYaoChanged(index: Int){
......
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

@Composable
internal fun GuaScreenRoute(
// di generate ViewModel
guaViewModel: GuaViewModel = hiltViewModel()
) {
guaViewModel.initDatabase()
// use ViewModel to provide UiState
val yaoExplainUiState by guaViewModel.getGuaExplainUiState.collectAsStateWithLifecycle()
val yaoUiState by guaViewModel.getYaoUIState.collectAsStateWithLifecycle()
GuaScreen(
testClick = guaViewModel::onSearchQueryChanged,
yaoChange = guaViewModel::onYaoChanged,
guaExplainUiState = yaoExplainUiState,
yaoUiState = yaoUiState
)
}

@Composable
internal fun GuaScreen(
testClick: (Int) -> Unit = {},
yaoChange: (Int) -> Unit = {},
guaExplainUiState: GuaExplainUiState = GuaExplainUiState.LoadFailed,
yaoUiState: GuaBaseUiState = GuaBaseUiState()
) {
......
}


Data Layer

This part is about modify date get from database and network.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

class DefaultDatabaseRepository @Inject constructor(
private val configUtils: ConfigUtils,
private val guaDao: GuaDao
) :
DatabaseRepository {
override fun guaTableInit() {
......
}

//sync data from database
override fun getGuaBaseInfo(imageList: List<Boolean>): Flow<GuaBaseResult> {
var images = ""
imageList.map {
images += if (it) "1" else "0"
}
val guaEntity = guaDao.getGuaByImage(images)
return flowOf(GuaBaseResult(guaEntity.id, guaEntity.name, guaEntity.detail, guaEntity.descGroup))
}
}


internal class DefaultGuaRepository @Inject constructor() :
GuaRepository {
// sync data from network
override suspend fun getExplainInfo(index: Int): Flow<GuaExplainResult> {
return flowOf(FakeNetwork.getGuaFakeExplain(index))
}
}

Domain Layer

This part info is less, it’s more like a interface definition.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class GetGuaBaseUseCase @Inject constructor(
private val databaseRepository: DatabaseRepository
) {
operator fun invoke(imageList: List<Boolean>): Flow<GuaBaseResult> =
databaseRepository.getGuaBaseInfo(imageList)
}

class GetGuaExplainUseCase @Inject constructor(
private val guaRepository: GuaRepository
) {
suspend operator fun invoke(index: Int): Flow<GuaExplainResult> =
guaRepository.getExplainInfo(index)
}

Dependency

Dagger basics

https://developer.android.com/training/dependency-injection/dagger-basics

It can help us to decouple ViewModel and View.

Final

“No silver bullet. No silver bullet”

All of the architecture design is base on the current project and developer’s mood, there is no silver bullet, only suitable for current project.

Code

Reference: (https://github.com/android/nowinandroid/tree/main)[https://github.com/android/nowinandroid/tree/main]
Demo: (https://github.com/clwater/AndroidUDF)[https://github.com/clwater/AndroidUDF]