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

Tomas Gordian

03/24/2022, 12:00 PM
Hello, why is recomposition happening in this example even when no parameters has changed and how can I avoid it?
In this example, I have two buttons. When I click for example on first button, my state changes, but I would expect only the FirstButton Composable to be recomposed because in SecondButton composable nothing changed.
Copy code
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val stateFlow = MutableStateFlow(State(0, 0))
        setContent {
            MyComposeTheme {
                val state by stateFlow.collectAsState()

                Column() {
                    FirstButton(
                        text = state.firstNumber.toString()
                    ) {
                        stateFlow.value = stateFlow.value.copy(firstNumber = Random.nextInt())
                    }

                    SecondButton(
                        text = state.secondNumber.toString()
                    ) {
                        stateFlow.value = stateFlow.value.copy(secondNumber = Random.nextInt())
                    }
                }

            }
        }
    }
}

@Composable
private fun FirstButton(
    text: String,
    onClick: () -> Unit
) {
    Button(
        onClick = onClick
    ) {
        Text(text = text)
    }
}

@Composable
private fun SecondButton(
    text: String,
    onClick: () -> Unit
) {
    Button(
        onClick = onClick
    ) {
        Text(text = text)
    }
}

data class State(
    val firstNumber: Int,
    val secondNumber: Int
)
t

Tobias Gronbach

03/24/2022, 12:42 PM
I guess it's because you read the value in the second button. Maybe this article helps : https://dev.to/zachklipp/scoped-recomposition-jetpack-compose-what-happens-when-state-changes-l78
z

Zach Klippenstein (he/him) [MOD]

03/24/2022, 3:15 PM
Yep that's exactly why
Function arguments in Kotlin are always passed by value, so they're evaluated before actually invoking the function they're being passed to. That means you're reading the state in the higher level composable here. But that's fine - there's nothing inherently wrong with this code. Compose will still skip things where it can.
t

Tomas Gordian

03/24/2022, 3:36 PM
I see, but what if I have a state and one of the fields is for example position in a video player that is updated very often. I am using this position in composable A. But then I have another field in this state - video size, that I am using in composable B. This means that composable B is also recomposed as much as composable A, even when it does not have to, moreover there is also an AndroidView in Composable B, where I am setting the video size inside update callback. Is it still fine in this case?
z

Zach Klippenstein (he/him) [MOD]

03/24/2022, 3:39 PM
Right, so that’s probably also fine, since you’d probably be passing the seek position into a composable as a regular param anyway. But if the state is used by a later phase (eg layout or drawing), then you can refactor how you store your state so you can defer the state read until the latest point where you actually need it and skip having to recompose at all when it changes. But in this case it’s not worth refactoring just for that reason.
t

Tobias Gronbach

03/24/2022, 3:44 PM
If those fields are put together like in a dataclass as state, then yes but maybe it could work if you extract one of those field and make it a state of its own by using derivedStateOf f.e
@Zach Klippenstein (he/him) [MOD] thank you for your great article I linked above. It really helped me out a lot! 👌👌👍
👍🏻 1
t

Tomas Gordian

03/24/2022, 4:02 PM
Got it, thank you 🙏
5 Views