0

I'm trying to figure out what the best practice is in ViewModel. StateFlow, LiveData or ComposeState. Now I have come across something that I can't explain.

Activity:

val viewModel by viewModels<MainViewModel>()

setContent {
      val randomAdmin by viewModel.randomAdmin.collectAsStateWithLifecycle()

      Spacer(modifier = Modifier.height(20.dp))
      Text(text = "Random Admin: ${randomAdmin.name} / ${randomAdmin.age}")
      BasicButton("ChangeAnyAdmib"){
        viewModel.changeRandomAdmin()
      }
}

ViewModel:

class MainViewModel : ViewModel() {

    private val _randomAdmin = MutableStateFlow( Admin("Peter",0) )
    val randomAdmin = _randomAdmin.asStateFlow()


    fun changeRandomAdmin() {
        _randomAdmin.value = _randomAdmin.value.copy(name = "Hans")
    }

}

Works well. The new admin will be emited. But when I change the ViewModel function code (see below), the new admin will not be emitted. Why? I have the same issue with LiveData & MutableState. It will not emited. Strangely enough, the reference is the same.

    fun changeRandomAdmin() {
        _randomAdmin.value.name = "Hans"
        val oldValue = _randomAdmin.value
        _randomAdmin.value = _randomAdmin.value.copy()
        Log.d("ViewModelTest" , "Same Object: " + (oldValue === _randomAdmin.value).toString()) // true
    }

But when I use this code (see below) the reference is not the same, but it should. Unfortunately the object is still not emitted

    fun changeRandomAdmin() {
        _randomAdmin.value.name = "Hans"
        val oldValue = _randomAdmin.value.copy()
        _randomAdmin.value = oldValue
        Log.d("ViewModelTest" , "Same Object: " + (oldValue === _randomAdmin.value).toString()) // false
    }

I no longer understand the world ;(

It should emit the new object, I thought.

Wolfscowl
  • 1
  • 1
  • In general, you can't mutate values from a StateFlow. If you need to copy, then you need to copy first and then assign the new value second, not the other way around. It will help if you make `Admin` immutable, so its properties are all vals instead of vars. – Louis Wasserman Apr 12 '23 at 12:14

2 Answers2

0

Have you tried to do:

fun changeRandomAdmin() {
    _randomAdmin.value = randomAdmin.value.copy(name = "Hans")
}
Manu
  • 629
  • 7
  • 6
0

In the first changeRandomAdmin function you are setting a new instance of Admin by using the copy function. This triggers an emit as those two Admins are not the same.

In the second and third ChangeRandomAdmin function you are not setting a new admin value directly to the StateFlow, but you mutate the existing one. This is not noticed by the StateFlow.

You have referential equality in the second changeRandomAdmin function for oldValue and _randomAdmin.value, because _randomAdmin.value = _randomAdmin.value.copy() has no effect, as the StateFlow checks for (structural) equality before emitting values, take a look at the docs for StateFlows section Strong equality-based conflation.

CodeX
  • 48
  • 6