Hi guys, So basically I have all my UI state encap...
# compose
e
Hi guys, So basically I have all my UI state encapsulated in a single data class and expose it to the composable UI via single state flow. If I want to modify the state I use
_state.value = _state.value.copy(...)
. Does something like this should be avoided or affecting performance?
j
If I’m right, I think you’re using the MVI pattern. The need to allocate a new instance every time to create a new state is inevitable and disadvantageous because all states are immutable in MVI.
🙏 1
m
I do things in a similar way and it works nicely. It results in a clean structure and, if done right, the copying overhead is neglectable. I also use https://github.com/Kotlin/kotlinx.collections.immutable in order to further optimize that.
🙏 1
j
Kotlin/kotlinx.collections.immutable is great information. thanks.
p
If you call copy on a data class, that does not mean that every object referred in the data class gets recreated
😯 1
So if you have a Person with a List of Addresses and a name, and you now change the name of a person you won't be allocating much
🙏 1
And usually the changes to a view state happen in reaction to a user event. Unless you are doing extremely much, the performance difference won't be measurable
👍 1
And much means sth in the order of hundred thousands of objects
e
Amazing, thank you!
d
StateFlow.update() should be used instead of .value directly when you do .copy()
👍 3
today i learned 3
e
Thanks, I've just read about race condition problem that may occur when use .value directly to copy https://proandroiddev.com/make-sure-to-update-your-stateflow-safely-in-kotlin-9ad023db12ba
👍 2
c
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:
Copy code
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