Hi all, I have a question about MVI vs MVVM architecture. Consider a large application, which has a ...
d

Dilraj Singh

over 2 years ago
Hi all, I have a question about MVI vs MVVM architecture. Consider a large application, which has a lot of elements and UI states, let's take 50 state elements as an example.
data class ExampleModel(
    val field1: Int = 0,
    val field2: Int = 0,
    val field3: Int = 0,
    ...
)
Typically we would model this as follows- 1. MVVM- we would have individual
liveData/stateFlow
for each state element, and whenever we want to update any state element, we would update the stateFlow for that particular element 2. MVI- we would encapsulate all the elements into a single
immutable data class
, and whenever we want to update any state element, we would make a copy of this data class object, and mutate the required property
The first question is whether MVI will cause a lot of memory overhead since we make a copy of the object every time we want to update any element of the state. In a timer application where the state can be modelled as
data class TimeModel(val remainingTime: String)
, and update the state as follows-
1. MVI-
stateFlow.update { timeModel.copy(remainingTime = timeModel.remainingTime - 1) }
2. MVVM-
stateFlow.update { it - 1 }
In both cases, we are making a new string object, but in MVI we are making a new object for
TimeModel
as well. And since kotlin introduced value classes to reduce the memory footprint of simple model classes, is MVI nullifying that advantage?
The second question is that while using jetpack compose, does it make more sense to use MVVM for bigger projects than MVI? In MVI, we would make a copy of the class object, thus changing its
hashCode
. So in every composable, we would need to have a
derivedStateOf
just to reduce the number of recompositions caused by other state updates, otherwise, it would trigger a recomposition even if some other state element is changing. Example below-
@Composable
fun ExampleComposable1(field1: () -> Int) {
    println("ExampleComposable1: recomposing")
    Text(text = "field 1 is ${field1()}")
}
@Composable
fun ExampleComposable2(field2: () -> Int) {
    println("ExampleComposable2: recomposing")
    Text(text = "field 2 is ${field2()}")
}
@Composable
fun ExampleComposable3(field3: () -> Int) {
    println("ExampleComposable3: recomposing")
    Text(text = "field 3 is ${field3()}")
}
data class ExampleModel(
    val field1: Int = 0,
    val field2: Int = 0,
    val field3: Int = 0,
)
var state by remember {
    mutableStateOf(ExampleModel())
}
Button(
    onClick = { state = state.copy(field1 = state.field1 + 1) }
) {
    Text(text = "update state 1")
}
ExampleComposable1(field1 = { state.field1 })
ExampleComposable2(field2 = { state.field2 })
ExampleComposable3(field3 = { state.field3 })
If we check the logs, we can see that all the 3 composables recompose every time even though we are updating only
field1
, and the other 2 are constant. To solve this problem, we would have to use
derivedStateOf
inside every composable to check if the value is changing or not, thus creating even more state objects, and increasing memory footprint. I understand this would not hamper an example application, but in a production application, where new features are added every day, this will become a huge problem.
🧵 1