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
test
complies with everything aboutdata class
, it creates a new object with a new value and that's whatCompose
needs to triggerre-composition
.The second
test
will not triggerre-composition
,Compose
still sees the sameIntWrapper
instance becausesadProperty
is 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