The overhead of
.copy()
-ing a data class is pretty negligible, overall. Iโve got a library,
Ballast, where Iโve used this pattern with UI updates that emit very frequently, and the pattern itself has never caused me any noticeable performance issues, even in situations where I thought it might because the updates were emitting so quickly. Switching threads to handle those updates is what makes your app feel slow (especially when typing with Compose), for example updating the StateFlow like this:
fun updateText(textFieldValue: TextFieldValue) {
viewModelScope.launch(Dispatchers.Default) { // don't do this, keep it on Dispatchers.Main
_state.update { it.copy(text = textFieldValue) }
}
}
Another thing to watch out for is multiple coroutines updating the state concurrently. Any single update done with
.update { }
will be thread-safe against race conditions, but the code you write can still be vulnerable to race conditions because coroutines are still concurrent, even on a single-threaded dispatcher. I have an example in the
Ballast docs demonstrating the problem, as well as some strategies for protecting against it, which are all supported out-of-the-box in Ballast