01/31/2023, 7:05 AM
I saw a new API for
here, and I’ve few questions 🧵
1. Why do we need a
? 2. Can you provide a proper usecase for this? The one that’s added in the doc looks copied from the old

Stylianos Gakis

01/31/2023, 10:08 AM
Seems like it’s used in the equals method, maybe as an optimization instead of having to compare everything else? This equals would be super fast compared to doing other checks. This test seems to show that the child composable “ModifiedComposable” skips recomposition here when
is called. Maybe without the full name qualifier it wouldn’t do that?

Ale Stamato

01/31/2023, 12:59 PM
I think this was the original request FYI @Adam Powell ----- Side note, from compose UI 1.3.0, new experimental API suite
is a higher-performance alternative to

Adam Powell

01/31/2023, 3:01 PM
@Stylianos Gakis is right on the money, and like @Ale Stamato said, we decided to take an alternative path with
instead of this

Stylianos Gakis

01/31/2023, 3:05 PM
Just so I understand how this works correctly. Is it maybe that this makes the composables read as stable, aka skipping in cases such in this test, since they don’t need their parent to know if there should be a recomposition. Instead, they themselves will know that they need to recompose if any of the state reads they do inform them that there’s been a change and they’ll recompose by themselves. Is this how the recomposition can be skipped without sacrificing correctness, or is it something else?
For this test, changing it from what it was to this makes it pass again
fun recomposingKeyedComposedModifierSkips() = runBlocking {
  // Manually invalidate the composition instead of using mutableStateOf
  // Snapshot-based recomposition requires explicit snapshot commits/global write observers.
  lateinit var scope: RecomposeScope

  val frameClock = TestFrameClock()
  withContext(frameClock) {
    withRunningRecomposer { recomposer ->
      var composeCount = 0
      var childComposeCount = 0
      // Use the same lambda instance; the capture used here is unstable
      // and would prevent skipping.
      val increment: (Modifier) -> Unit = { childComposeCount++ }
      // <-- change, removed key as it was unused

      compose(recomposer) {
        scope = currentRecomposeScope
        SideEffect { composeCount++ }
        ModifiedComposable(Modifier.composed { Modifier }, increment) // <-- change

      assertEquals("initial compositions", 1, composeCount)
      assertEquals("initial child compositions", 1, childComposeCount)


      assertEquals("recomposed compositions", 2, composeCount)
      assertEquals("recomposed child compositions", 2, childComposeCount) // <-- change
Changes are that I used the old Modifier.composed without the qualified name + key And then the assertion now checks that childComposeCount is 2, not 1 like it was before. Meaning that “recomposed child compositions” also increments to 2, showing the non-skippability of the old composed modifier. I would definitely like to see some concrete examples in the docs though as theapache mentioned