Status Update
Comments
lp...@google.com <lp...@google.com> #2
Branch: androidx-main
commit dce95f0a931da95ad38e24c94d3496241e0c1954
Author: Andrey Kulikov <andreykulikov@google.com>
Date: Wed Jan 26 19:38:35 2022
Support content types in Lazy grids
Relnote: You can now specify the content type for the items of LazyVerticalGrid - item/items functions on LazyGridScope now accept such parameter. Providing such information helps item composition reusing logic to make it more efficiently and only reuse the content between the items of similar type.
Fixes: 215372836
Test: LazyGridSlotsReuseTest
Change-Id: I7b3550cf626b6ef6f65029b1e55465266bfacb18
M compose/foundation/foundation/api/public_plus_experimental_current.txt
M compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyGrid.kt
M compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridItemsProviderImpl.kt
M compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridScopeImpl.kt
A compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridSlotsReuseTest.kt
je...@google.com <je...@google.com> #3
Hi, I can't reproduce this behavior. It is possible that the composable in your code is recomposing because of a different reason. Is the tag you pass for example read from a class variable? That would cause your composable to be recomposed because the compiler cannot infer that all input to the testTag is @Stable.
The test below shows that adding testTag
does not prevent Compose to skip the composable:
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.width
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.produceState
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.delay
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class TestTagTest {
@get:Rule
val rule = createComposeRule()
@Stable
private class RecompositionCounter {
var withoutTag = 0
var withTag = 0
}
@Test
fun test() {
val counter = RecompositionCounter()
rule.setContent {
val state = produceState(initialValue = 0) {
repeat(10) {
delay(500)
value++
}
}
Column {
Text(text = "value: ${state.value}")
MyComponent(modifier = Modifier.width(200.dp), "Data1", counter)
MyComponent(modifier = Modifier.width(200.dp).testTag("Data2"), "Data2", counter)
}
}
repeat(10) {
rule.waitForIdle()
rule.mainClock.advanceTimeBy(500)
}
rule.onNodeWithText("value: 10").assertExists()
assertThat(counter.withoutTag).isEqualTo(1)
assertThat(counter.withTag).isEqualTo(1)
}
@Composable
private fun MyComponent(modifier: Modifier = Modifier, text: String, rc: RecompositionCounter) {
if (text == "Data1") {
rc.withoutTag++
} else if (text == "Data2") {
rc.withTag++
}
Text(modifier = modifier, text = text)
}
}
an...@telekom.de <an...@telekom.de> #4
Hi,
I have change the code a bit so I can use it outside a test on a ComponentActivity.
Here the code:
package de.telekom.testapp
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.width
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.delay
@Stable
private data class RecompositionCounter(
var withoutTag: Int = 0,
var withTag: Int = 0
)
@Composable
fun TestModifier() {
val counter = remember { RecompositionCounter() }
val state = produceState(initialValue = 0) {
repeat(10) {
delay(500)
value++
}
}
Column {
Text(text = "value: ${state.value} $counter")
MyComponent(modifier = Modifier.width(200.dp), "Data1", counter)
MyComponent(
modifier = Modifier
.width(200.dp)
.testTag("Data2"), "Data2", counter
)
}
}
@Composable
private fun MyComponent(modifier: Modifier = Modifier, text: String, rc: RecompositionCounter) {
if (text == "Data1") {
rc.withoutTag++
} else if (text == "Data2") {
rc.withTag++
}
Text(modifier = modifier, text = text)
}
And the counter "withTag" still increments. Attached you find a screenshot after 10 repeats.
an...@telekom.de <an...@telekom.de> #5
Additional info
compose version: '1.1.0-beta02' gradle: gradle-7.2-all Emulator: API 26 x86
je...@google.com <je...@google.com> #6
Hmm, this is odd. I can reproduce the bug when using the latest snapshot build of Compose from androidx.dev (
je...@google.com <je...@google.com> #7
Here is what happened:
The Live Literals feature in Android Studio switches out literals with state values. This will influence static inference by the compiler.
Without Live Literals on, the compiler will infer that Modifier.testTag("tag")
is only using static input and will not even call equals on it to verify if the value has changed (and consequently if parts of the code can be skipped); it knows that it will never change. With Live Literals on however, Modifier.testTag("tag")
is no longer considered static and during composition Modifier.testTag("tag") == Modifier.testTag("tag")
will be false, resulting in not skipping that part of the code.
This is a consequence of the underlying Modifier.composed(..)
, which is not equal to another Modifier.composed(..)
with exactly the same lambda.
For now, you can turn off Live Literals by adding @file:NoLiveLiterals
to the impacted files when benchmarking compositions. Also, in release builds Live Literals will always be turned off.
I filed
an...@telekom.de <an...@telekom.de> #8
In release builds or with @file:NoLiveLiterals it works.
Thanks for the details.
ap...@google.com <ap...@google.com> #9
Branch: androidx-main
commit 23ca74cafdbfa98898d8dfd5836ce81f02f48a59
Author: Alexandre Elias <aelias@google.com>
Date: Wed Jul 13 15:52:22 2022
Make Modifier.semantics stateless
This consolidates two other changes (
composed {} from the semantics modifier.
The semantics id is now generated lazily in the corresponding
LayoutNode. This allows us to create the semantics modifier without the
need for a composition context. In turn that means that semantics
modifiers are now comparable with each other, such that for example
Modifier.testTag("foo") == Modifier.testTag("foo").
When lambdas are part of the semantics, for example as an onClick
action, the semantics modifiers will still compare unequal though, even
when the lambdas do the exact same thing:
Modifier.semantics{onClick={true}} != Modifier.semantics{onClick={true}}
Bug: 203559524
Test: ./gradlew compose:ui:ui:test && \
./gradlew compose:ui:ui:cC
Relnote: "Deprecated `SemanticsModifier.id` and moved the semantics id
to `LayoutInfo.semanticsId` instead."
Change-Id: Iac808fc0e3ff14f1c1a95ee3f1f24cd436245a0e
M compose/ui/ui/api/restricted_current.ignore
M compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTree.kt
M compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt
M compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
M compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsModifier.kt
M compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/TextControllerTest.kt
M compose/ui/ui/api/current.txt
M compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/semantics/SemanticsTests.kt
M compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/SkiaBasedOwner.skiko.kt
M compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
M compose/ui/ui/api/current.ignore
M compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
M compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
M compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LayoutInfo.kt
M compose/ui/ui/api/restricted_current.txt
M compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/TextSelectionLongPressDragTest.kt
M compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsNode.kt
M compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsEntity.kt
M compose/ui/ui/api/public_plus_experimental_current.txt
cl...@google.com <cl...@google.com>
ae...@google.com <ae...@google.com> #10
I didn't specifically test the testTag/skipping combination, but I think the above change probably fixed it in Compose 1.3. Feel free to reopen if it didn't.
Description
I have created a code sample which shows the wrong behavior of the
testTag
modifier.Steps to Reproduce:
Compile this code and run the composable.
Look at the debug console. If the
testTag
is set to the modifier chain the composable is recomposed every time the state changes.