I think i'm missing a core concept of Jetpack Compose here. I'm running into an issue when i'm trying to change a non-constructor data class property inside of a composable when this composable is part of an observed list.
Does not work: (sadProperty is not declared in the constructor)
data class IntWrapper(val actualInt: Int = 0) {
var sadProperty: Int = 0
}
@Preview
@Composable
fun test() {
var state by remember { mutableStateOf(listOf(IntWrapper(1), IntWrapper(2), IntWrapper(3),IntWrapper(4)))}
fun onClick(item: IntWrapper) {
val indexOf = state.indexOf(item)
val newState = state.minus(item).toMutableList()
val copy = item.copy()
copy.sadProperty = Random.nextInt()
newState.add(indexOf, copy)
state = newState
}
Column() {
for (item in state) {
Text("ac: ${item.actualInt} sad: ${item.sadProperty}", modifier = Modifier.clickable { onClick(item)})
}
}
}
Works: (actualInt is declared in the constructor)
data class IntWrapper(var actualInt: Int = 0) {
var sadProperty: Int = 0
}
@Preview
@Composable
fun test() {
var state by remember { mutableStateOf(listOf(IntWrapper(1), IntWrapper(2), IntWrapper(3),IntWrapper(4)))}
fun onClick(item: IntWrapper) {
val indexOf = state.indexOf(item)
val newState = state.minus(item).toMutableList()
val copy = item.copy()
copy.actualInt = Random.nextInt()
newState.add(indexOf, copy)
state = newState
}
Column() {
for (item in state) {
Text("ac: ${item.actualInt} sad: ${item.sadProperty}", modifier = Modifier.clickable { onClick(item)})
}
}
}
Could somebody explain why this happens?
CodePudding user response:
This looks like a question of both Jetpack Compose and about Kotlin data class, bare with me, I'll try my best.
Lets start with Kotlin's Data classes first
As per the kotlin docs about Data Class
The compiler automatically derives the following members from all properties declared in the primary constructor:
- equals()/hashCode() pair
- toString() of the form "User(name=John, age=42)"
- componentN() functions corresponding to the properties in their order of declaration.
- copy() .
Your IntWrapper data class has one Primary Constructor, the parenthesis that follows the class name, and with 1 property declared inside of it.
data class IntWrapper(val actualInt: Int = 0) {
var sadProperty: Int = 0
}
with that, we can say, your IntWrapper data class has
- 1 component (
actualInt) - toString() of the form
IntWrapper(actualInt=?) - a generated
copy()function - a generated equals()/hashCode() pair
and based again from the docs:
The compiler only uses the properties defined inside the primary constructor for the automatically generated functions. To exclude a property from the generated implementations, declare it inside the class body:
The equals will only use/evaluate the property declared from IntWrapper's primary constructor (i.e actualInt : Int), and sadProperty is excluded from it because its in the part of the data class body.
Now consider the following:
val intWrapper1 = IntWrapper(actualInt = 5)
intWrapper1.sadProperty = 5
val intWrapper2 = IntWrapper(actualInt = 5)
intWrapper2.sadProperty = 10
Log.e("AreTheyEqual?", "${intWrapper1 == intWrapper2}")
it prints,
E/AreTheyEqual?: true
because the equality sees both of the derived properties have the same value 5, sadProperty is excluded from this comparison.
val intWrapper1 = IntWrapper(actualInt = 5)
intWrapper1.sadProperty = 5
val intWrapper2 = IntWrapper(actualInt = 10)
intWrapper2.sadProperty = 5
prints,
E/AreTheyEqual?: false
because the generated equals verifies that the generated component (actualInt) is NOT the same from the two IntWrapper instance.
Now going to Jetpack Compose, applying everything we understand with data classes,
The first
testcomplies with everything aboutdata class, it creates a new object with a new value and that's whatComposeneeds to triggerre-composition.The second
testwill not triggerre-composition,Composestill sees the sameIntWrapperinstance becausesadPropertyis not part of the generated components that will be used by the data class's equals operation.
CodePudding user response:
In Compose, you must use one of the following two methods to perform the recomposition operation successfully:
1 - using mutableStateListOf(), however, by updating the value of the item in the list, the recomposition operation is performed
2- Using your own method that you posted
But for the second method, you need to tell Compose that actualInt has changed, so you need to create a new instance of int.
If you don't want to do this, you need to explain your scenario more so that I can provide a more complete guide
