Avoid relayouts from unchanged semantics modifiers
Adding an equals() implementation to SemanticsModifierCore means layout
composables won't rerun unnecessarily every time a new one is
constructed with the exact same properties.
Fixes: 162006751
Test: unchangedSemanticsDoesNotCauseRelayout
Change-Id: I7d0d20ea5b3080e84e3b0faf606cf73c38b4a62f
diff --git a/ui/ui-core/src/androidAndroidTest/kotlin/androidx/compose/ui/semantics/SemanticsTests.kt b/ui/ui-core/src/androidAndroidTest/kotlin/androidx/compose/ui/semantics/SemanticsTests.kt
index b7d8053..7939b4d 100644
--- a/ui/ui-core/src/androidAndroidTest/kotlin/androidx/compose/ui/semantics/SemanticsTests.kt
+++ b/ui/ui-core/src/androidAndroidTest/kotlin/androidx/compose/ui/semantics/SemanticsTests.kt
@@ -19,6 +19,7 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.Stable
import androidx.test.filters.MediumTest
import androidx.ui.core.Layout
import androidx.ui.core.Modifier
@@ -43,6 +44,7 @@
import androidx.ui.test.runOnIdle
import androidx.ui.test.runOnUiThread
import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -69,6 +71,22 @@
}
@Test
+ fun unchangedSemanticsDoesNotCauseRelayout() {
+ val layoutCounter = Counter(0)
+ val recomposeForcer = mutableStateOf(0)
+ composeTestRule.setContent {
+ recomposeForcer.value
+ CountingLayout(Modifier.semantics { accessibilityLabel = "label" }, layoutCounter)
+ }
+
+ runOnIdle { assertEquals(1, layoutCounter.count) }
+
+ runOnIdle { recomposeForcer.value++ }
+
+ runOnIdle { assertEquals(1, layoutCounter.count) }
+ }
+
+ @Test
fun nestedMergedSubtree() {
val tag1 = "tag1"
val tag2 = "tag2"
@@ -322,6 +340,21 @@
assert(SemanticsMatcher.keyNotDefined(property))
}
+// Falsely mark the layout counter stable to avoid influencing recomposition behavior
+@Stable
+private class Counter(var count: Int)
+
+@Composable
+private fun CountingLayout(modifier: Modifier, counter: Counter) {
+ Layout(
+ modifier = modifier,
+ children = {}
+ ) { _, constraints ->
+ counter.count++
+ layout(constraints.minWidth, constraints.minHeight) {}
+ }
+}
+
/**
* A simple test layout that does the bare minimum required to lay out an arbitrary number of
* children reasonably. Useful for Semantics hierarchy testing
diff --git a/ui/ui-core/src/commonMain/kotlin/androidx/ui/core/semantics/SemanticsModifier.kt b/ui/ui-core/src/commonMain/kotlin/androidx/ui/core/semantics/SemanticsModifier.kt
index c14fe67..4417986 100644
--- a/ui/ui-core/src/commonMain/kotlin/androidx/ui/core/semantics/SemanticsModifier.kt
+++ b/ui/ui-core/src/commonMain/kotlin/androidx/ui/core/semantics/SemanticsModifier.kt
@@ -56,6 +56,20 @@
private var lastIdentifier = AtomicInt(0)
fun generateSemanticsId() = lastIdentifier.addAndGet(1)
}
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is SemanticsModifierCore) return false
+
+ if (id != other.id) return false
+ if (semanticsConfiguration != other.semanticsConfiguration) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ return 31 * semanticsConfiguration.hashCode() + id.hashCode()
+ }
}
/**