I'm still a bit of a beginner with Jetpack compose and understanding how re-composition works.
So I have a piece of code calls below inside a ViewModel
.
SnapshotStateList
var mutableStateTodoList = mutableStateListOf<TodoModel>()
private set
during construction of the view model, I execute a room database call
init {
viewModelScope.launch {
fetchTodoUseCase.execute()
.collect { listTypeTodo ->
mutableStateTodoList = listTypeTodo.toMutableStateList()
}
}
}
then I have an action from a the ui that triggers adding of a new Todo to the list and expecting a re-composition from the ui that shows a card composable
fun onFabClick() {
todoList.add(TodoModel())
}
I can't figure out why it doesn't trigger re-composition.
However if I modify the init code block below, and invoke the onFabClick()
action, it triggers re-composition
init {
viewModelScope.launch {
fetchTodoUseCase.execute()
.collect { listTypeTodo ->
mutableStateTodoList.addAll(listTypeTodo)
}
}
}
or this, taking out the re-assigning of the mutableStateList
outside of the coroutine scope also works (triggers re-composition).
init {
// just trying to test a re-assigning of the mutableStateList property
mutableStateTodoList = emptyList<TodoModel>().toMutableStateList()
}
Not quite sure where the problem if it is within the context of coroutine or SnapshotStateList
itself.
Everything is also working as expected when the code was implemented this way below, using standard list inside a wrapper and performing a copy (creating new reference) and re-assigning the list inside the wrapper.
var todoStateWrapper by mutableStateOf<TodoStateWrapper>(TodoStateWrapper)
private set
Same init{...} call
init {
viewModelScope.launch {
fetchTodoUseCase.execute()
.collect { listTypeTodo ->
todoStateWrapper = todoStateWrapper.copy (
todoList = listTypeTodo
)
}
}
}
To summarize, inside a coroutine scope, why this works
// mutableStateList
todoList.addAll(it)
while this one does not?
// mutableStateList
todoList = it.toMutableStateList()
also why does ordinary list inside a wrapper and doing copy()
works?
CodePudding user response:
The mutable state in Compose can only keep track of updates to the containing value. Here is simplified code on how MutableState
could be implemented:
class MutableState<T>(initial: T) {
private var _value: T = initial
private var listeners = MutableList<Listener>
var value: T
get() = _value
set(value) {
if (value != _value) {
_value = value
listeners.forEach {
it.triggerRecomposition()
}
}
}
fun addListener(listener: Listener) {
listeners.add(listener)
}
}
When the state is used by some view, this view subscribes to updates of this particular state.
So, if you declare the property as follows:
var state = MutableState(1)
and try to update it with state = 2.toMutableState()
(this is analogous to your mutableStateTodoList = listTypeTodo.toMutableStateList()
), triggerRecomposition
cannot be called because you create a new object which resets all the listeners. Instead, to trigger recomposition you should update it with state.value = 2
.
With mutableStateList
, the analog of updating value is any method of MutableList
interface that updates containing list, including addAll
.
Inside init
it works because no view is subscribed to this state so far, and that's the only place where methods such as toMutableStateList
should be used.
It is important to always define mutable states as immutable property with val
in order to prevent such mistakes. To make it mutable only from view model, you can define it like this, and make updates on _mutableStateTodoList
:
private val _mutableStateTodoList = mutableStateListOf<TodoModel>()
val mutableStateTodoList: List<TodoModel> = _mutableStateTodoList
The only exception when you can use var
is using mutableStateOf
with delegation - this is where you can use it with private set
because in that case the delegation does the work for you by not modifying the container, but only it's value
property. Such method cannot be applied to mutableStateListOf
, because there's no single value
field that's responsible for the data in case of list.
var someValue by mutableStateOf(1)
private set