https://kotlinlang.org logo
#compose
Title
# compose
e

Eko Prasetyo

05/22/2022, 4:31 AM
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

Ji Sungbin

05/22/2022, 5:39 AM
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

Michael Paus

05/22/2022, 7:50 AM
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

Ji Sungbin

05/22/2022, 7:53 AM
Kotlin/kotlinx.collections.immutable is great information. thanks.
p

Paul Woitaschek

05/22/2022, 9:03 AM
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

Eko Prasetyo

05/22/2022, 10:03 AM
Amazing, thank you!
d

dorche

05/22/2022, 11:57 AM
StateFlow.update() should be used instead of .value directly when you do .copy()
👍 3
today i learned 3
e

Eko Prasetyo

05/22/2022, 12:11 PM
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

Casey Brooks

05/23/2022, 3:38 PM
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
4 Views