Search code examples
androidandroid-jetpack-composeandroid-jetpack

How to notify about state change if it is an instance of a class in jetpack compose


I've got code like this:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            TestsTheme {
                var myvalue = remember{ mutableStateOf(4)}
                Row() {
                    CustomText(myvalue = myvalue)
                    CustomButton(myvalue = myvalue)
                }
            }
        }
    }
}

@Composable
fun CustomButton(myvalue: MutableState<Int>) {
    Button(onClick = {myvalue.value++}) {

    }
}

@Composable
fun CustomText(myvalue: MutableState<Int>) {
    Text("${myvalue.value}")
}

When I click CustomButton, CustomText recomposes and changes it's value (and it is ok), but if I replace myvalue with an object of some class it does'nt work

How do I make the CustomText recompose if myvalue is now an instance of TestClass and it changes in the code below?

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            TestsTheme {
                var myvalue = remember{ mutableStateOf(TestClass(4))}
                Row() {
                    CustomText(myvalue = myvalue)
                    CustomButton(myvalue = myvalue)
                }
            }
        }
    }
}

@Composable
fun CustomButton(myvalue: MutableState<TestClass>) {
    Button(onClick = {myvalue.value.a++}) {

    }
}

@Composable
fun CustomText(myvalue: MutableState<TestClass>) {
    Text("${myvalue.value.a}")
}

data class TestClass(var a: Int) {

}

Solution

  • Compose can only understand that it should recompose if you set the value of a State object. In your example myvalue.value++ is really just short for myvalue.value = myvalue.value.inc(), so what triggers the recomposition is calling the state.value setter.
    If you just mutate a property of the object held by a state, you are not calling the setter and no recomposition happens.

    So you have 2 options to solve this:

    1. Make TestClass immutable and update the State with a new object
    data class TestClass(val a: Int) { // << changed var to val
    
    }
    
    @Composable
    fun CustomButton(myvalue: MutableState<TestClass>) {
        Button(onClick = {
            val current = myvalue.value
            myvalue.value = current.copy(a = current.a + 1)
        }) {
    
        }
    }
    

    This is not really nice to read but doesn't require TestClass to contain any compose-specific code.

    1. Make TestClass a state holder
    class TestClass(a: Int) { 
        var a by mutableStateOf(a)
    }
    
    
    @Composable
    fun Parent(){
        val myvalue = remember{ TestClass(4) } // no mutableStateOf, state is managed inside the object
        CustomButton(myvalue = myvalue)
    }
    @Composable
    fun CustomButton(myvalue: TestClass) {
        Button(onClick = { myvalue.a++ }) {
    
        }
    }