androidkotlinandroid-jetpack-composekotlin-stateflowmutablemap

MutableMap State value not updated on the UI (Kotlin Jetpack Compose)


I'm using a MutableMap to save the data which is updates every 5 seconds and I expect also to be updated the UI showing part of the Map. After trying different solution I found that if I add another Double variable both Map and Double are updated on the UI but if I keep only Map variable nothing changes:

ViewModel Variables:

private val _stockRealPriceFlow = MutableStateFlow<MutableMap<String, Double>>(mutableMapOf())
val stockRealPriceFlow: StateFlow<MutableMap<String, Double>> = _stockRealPriceFlow.asStateFlow()

private val _draftPrice = MutableLiveData<Double>()
val draftPrice: LiveData<Double> = _draftPrice

ViewModel Variables Update:

_stockRealPriceFlow.value[it.name] = it.currentPrice()
_draftPrice.value = stockRealPriceFlow["Draft"] ?: 0.0

Activity:

val stockRealPriceFlow by viewModel.stockRealPriceFlow.collectAsState()
val draftPrice by viewModel.draftPrice .observeAsState(0.0)

Column(
    modifier = Modifier
        .padding(8.dp)
) {
    Text(text = stockRealPriceFlow["Draft"].toString())
    Text(text = draftPrice.toString())
}

How that can be explained if the MutableMap variable is same in both cases? What is the correct way of working with the map on this case?

I expect UI always show the Map values.


Solution

  • The StateFlow doesn't provide a new value, that's why your UI won't update. Instead, the same MutableMap is updated with new/changed values. That won't trigger a new value for the flow, though.

    The solution is to never expose mutable values in your view model:

    private val _stockRealPriceFlow = MutableStateFlow<Map<String, Double>>(emptyMap())
    val stockRealPriceFlow: StateFlow<Map<String, Double>> =
        _stockRealPriceFlow.asStateFlow()
    

    To update the map of the mutable flow you need to replace the map with a new one that contains the updated element. Instead of setting _stockRealPriceFlow.value[it.name] = it.currentPrice() you should use this:

    _stockRealPriceFlow.update { oldMap->
        oldMap + (it.name to it.currentPrice())
    }