1

I created an app that just displays one icon, two texts, and two buttons. When by click on any button, the appropriate text updates, because it depends on the value passed in my function. But together with text, each time icon recomposes, it even doesn't depend on any value.

data class TestUIState(
    val counterA: Int = 0,
    val counterB: Int = 0,
)

@HiltViewModel
class TestViewModel @Inject constructor() : ViewModel() {
    val uiFlow = MutableStateFlow(TestUIState())
    val data: TestUIState
        get() = uiFlow.value


    fun increaseCounterA() {
        uiFlow.value = data.copy(counterA = data.counterA + 1)
    }

    fun increaseCounterB() {
        uiFlow.value = data.copy(counterB = data.counterB + 1)
    }
}

@Composable
fun TestCompose(
    uiValueState: State<TestUIState>,
    increaseCounterA: () -> Unit,
    increaseCounterB: () -> Unit,
) {
    Column(modifier = Modifier.fillMaxSize()) {
        Icon(
            painter = painterResource(id = R.drawable.ic_google),
            contentDescription = "",
        )
        Text(text = "CounterA: ${uiValueState.value.counterA}", color = Color.White)
        Text(text = "Counter B: ${uiValueState.value.counterB}", color = Color.White)
        Button(onClick = increaseCounterA) {
            Text(text = "Increase counter A")
        }
        Button(onClick = increaseCounterB) {
            Text(text = "Increase counter B")
        }
    }
}
setContent {
                val testViewModel: TestViewModel = hiltViewModel()
                TestCompose(
                    uiValueState = testViewModel.uiFlow.collectAsStateWithLifecycle(),
                    increaseCounterA = testViewModel::increaseCounterA,
                    increaseCounterB = testViewModel::increaseCounterB
                )
}

When I press the update counter A button, I see the next in the layout inspector: recomposition counts

Then when I press the update counter B button, the icon recomposes again. recomposition counts 2

I just can't understand why a static image recomposes each time when some value even not related to this icon has changed. Or can it be expected behavior for this element? I'm not sure. Please help me to understand this issue.

Ruslan Leshchenko
  • 4,043
  • 4
  • 24
  • 36

1 Answers1

3

When a State is read inside a scope Composables that are not skippable get recomposed even if their inputs don't change.

Reading state.value in either of Text composable triggers recomposition for TestCompose scope. A scope is non-inline function that returns Unit. Layout inspector shows Composables with inline, which are not a scope, as you can see with Column. You can check out scoped recomposition in this answer and articles linked.

Jetpack Compose Smart Recomposition

When either of Text functions input change with corresponding Button changing counter A or B both of them do not recomposed on every recomposition.

However, Icon has unstable Painter param and because of that even if you pass same Painter it gets recomposed on every recomposition in TestCompose scope.

If you create function that takes painter as param

@Composable
private fun MyIcon(
    painter:Painter
) {

    Icon(
        painter = painter,
        contentDescription = "",
    )
}

It will log

restartable scheme("[androidx.compose.ui.UiComposable]") fun MyIcon(
  unstable painter: Painter
)

You can check out this article from Ben Trengrove if you wish to learn more about stability.

Thracian
  • 43,021
  • 16
  • 133
  • 222
  • Thanks for your response. I got you. But honestly, for me, it looks like a bug. Why painter with the same id is unstable....but ok. One more question, how you got a log that marks parameters as unstable? Just wanna know how to do it for my functions – Ruslan Leshchenko Aug 01 '23 at 07:00
  • If you wish to make it stable you can crate a scoped function as MyIcon and pass a data class with immutable painter param and @Immutable annotation – Thracian Aug 01 '23 at 07:02
  • 1
    How you log functions and types is available in this article. https://medium.com/androiddevelopers/jetpack-compose-stability-explained-79c10db270c8 – Thracian Aug 01 '23 at 07:04
  • 1
    Yeah, but again using annotations and writing custom functions for just displaying simple icons looks like workarounds for me, and we just fix bugs in Icon implementation. IMHO – Ruslan Leshchenko Aug 01 '23 at 07:16