Does a ViewModel member have to be defined to be o...
# compose
m
Does a ViewModel member have to be defined to be of a State<> type in order to invoke recomposion on change?
n
a state will trigger recomposition on change, but it doesn't have to be on the viewmodel part, ie collectAsState;observeAsState;produceState which will transform anything in a state that will trigger recomposition
1
your viewmodel member should be a flow or livedata or anything observable though, if it's just a var String for example, you should probably use a State (mutableStateOf) as you said, or it won't trigger any recomposition on the UI part
1
m
I have a list stored in my ViewModel, it is LiveData<String>, and using observeAsState() does not give me the updated list in the Compose code.
Without the code it can be hard to understand what happens but if you use a MutableList it can cause this issue, even though with a LiveData I think it should work 🤔
m
Hmm, I am seeing some red warnings in the doc 😄 Maybe that includes what I have done. Pasting the code in a bit.
Copy code
class MainViewModel : ViewModel() {
    // TODO: Implement the ViewModel

    private val _currentMessage: MutableLiveData<String> = MutableLiveData("")
    private val _messages: MutableLiveData<MutableList<String>> = MutableLiveData()

    val messages: LiveData<MutableList<String>> = _messages

    val currentMessage: LiveData<String> = _currentMessage


    fun setCurrentMessage(message:String){
        _currentMessage.value = message
    }

    fun onAddMessage(message: String){
//        val temp = mutableListOf(message).apply{
//            _messages.value?.let { addAll(it) }
//        }
//        _messages.value = temp
        _messages.value?.add(message)
        Log.d("MainViewModel", "onAddMessage: New message added: $message. Total: ${_messages.value?.size}")
    }
}
Code that uses it:
Copy code
Column{
    Log.d("MainFragment", "onCreateView: number of messages: ${messages?.size}")
    messages?.forEach { msg -> Text(msg, Modifier.padding(8.dp)) }
}
n
I think your issue is _messages contains no value 🙂
Copy code
private val _messages: MutableLiveData<MutableList<String>> = MutableLiveData()
-> _messages is empty, you need to either use MutableLiveData(mutableList()) or pass the list (as in your commented code)
but honnestly if you are doing a 100% compose new project, you should probably use
val message = mutableStateListOf()
3
m
I assume _messages should be updated with ViewModel.onAddMessage(message).
👍 1
z
A mutable list inside a state holder (a MutableState, StateFlow, LiveData, etc) is always a smell. Either use an immutable list or that mutableStateListOf. Assigning the same instance of a list to a state holder generally won't re-emit because the same list instance is always equal to itself.
1
c
A mutable list inside a state holder (a MutableState, StateFlow, LiveData, etc) is always a smell.
Makes sense.
Either use an immutable list
ok
or that mutableStateListOf
I would assume mutableStateListOf is the thing I should reach for first typically?
z
I don’t think there's a single right answer. Depends on other things, there are trade-offs as with anything. Immutable data structures are often generally easier to reason about in async/concurrent environments.
👍 1
1
c
Yeah, typically I've always chosen immutable first... but compose has thrown me in a loop because of snapshot state that basically acts like it's mutable and it feels like its magic to not have to call .copy eveywhere lol
z
Not sure if this will help but I wrote something about this yesterday: https://dev.to/zachklipp/two-mutables-dont-make-a-right-2kgp
2