Status Update
Comments
ap...@google.com <ap...@google.com> #2
We might just need to implement equals and hashcode for the modifier
ap...@google.com <ap...@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)
}
}
ap...@google.com <ap...@google.com> #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.
ap...@google.com <ap...@google.com> #5
Additional info
compose version: '1.1.0-beta02' gradle: gradle-7.2-all Emulator: API 26 x86
Description
Implement the new focus API where the API is split across multiple modifiers.