I'm having an issue with the `androidx.compose.ui....
# compose
d
I'm having an issue with the
androidx.compose.ui.test
library that I'm looking for advice on. Does performing a click action on a node using
onNodeWithTag
actually pass that click to the children of that node? In my example, the node with tag is a composable that applies the
fastClickable
modifier on a column (so the entire view is clickable) but the test does not actually click the component with the tag. In the past I've been able to get the click to work, but only by directly accessing the tag on a
Button
composable. More code for context in the thread
Copy code
@Composable
fun NodeWithTag(
    modifier: Modifier = Modifier,
    onToggle: () -> Unit,
    (... other args)
) {
    Column(
        modifier = Modifier
            .background(backGroundColor, itemShape)
            .border(1.dp, borderColor, itemShape)
            .fastClickable(MutableInteractionSource()) {
                onToggle()
            }
            .padding(1.5.paddingDp, 1.paddingDp, 1.5.paddingDp, 1.5.paddingDp)
            .then(modifier),
    ) {
        // column content
    }
}
My initial thought it maybe it has something to do with the order of the modifier (being after the click listener is set) so maybe it doesn't have the tag?
I've also tried adding a test tag to the column as well, before the modifier is applied and still the click does not go through:
Copy code
@Composable
fun NodeWithTag(
    modifier: Modifier = Modifier,
    onToggle: () -> Unit,
    nodeName: String
    (... other args)
) {
    Column(
        modifier = Modifier
            .testTag("nodeWithTag_$nodeName")
            .background(backGroundColor, itemShape)
            .border(1.dp, borderColor, itemShape)
            .fastClickable(MutableInteractionSource()) {
                onToggle()
            }
            .padding(1.5.paddingDp, 1.paddingDp, 1.5.paddingDp, 1.5.paddingDp)
            .then(modifier),
    ) {
        // column content
    }
}
for more context, when I print the tree (unmerged = true) I do not see onClick in the action list - could that indicate that it doesn't know how to perform a click on this node?
Copy code
|-Node #24 at (l=16.0, t=602.0, r=304.0, b=686.0)px, Tag: 'nodeWithTag_node1'
    | MergeDescendants = 'true'
    |  |-Node #27 at (l=28.0, t=611.0, r=34.0, b=646.0)px
    |  | Text = '[node1]'
    |  | Actions = [GetTextLayoutResult]
    |  |-Node #28 at (l=42.0, t=611.0, r=44.0, b=646.0)px
    |  | Text = '[3%]'
    |  | Actions = [GetTextLayoutResult]
    |  |-Node #29 at (l=44.0, t=610.0, r=292.0, b=646.0)px
    |    Text = '[0:17]'
    |    Actions = [GetTextLayoutResult]
z
What is
fastClickable
?
d
my bad, didn't realize that was our internal modifier. Here is the code for that
Copy code
/**
 * Currently compose clickable has an internal delay of 100ms in order to determine whether a press is follow by
 * scrollable behavior, and then emits the press state follow by the tap behavior. This will result in a slight UI
 * delay that is visible when there is an onPressed interaction. This fastClickable modifier will simply observe
 * the PressInteractions' states and emit appropriately without any locking.
 *
 * @see androidx.compose.foundation.handlePressInteraction
 */
@SuppressLint("UnnecessaryComposedModifier")
fun Modifier.fastClickable(
    interactionSource: MutableInteractionSource,
    enabled: Boolean = true,
    role: Role? = null,
    onClick: () -> Unit
) = composed {
    val onClickState = rememberUpdatedState(onClick)

    val semanticModifier = Modifier.semantics(mergeDescendants = true) {
        if (role != null) {
            this.role = role
        }
    }

    val pointerInputModifier = pointerInput(interactionSource, enabled) {
        if (enabled) {
            detectTapGestures(
                onPress = { offset ->
                    val press = PressInteraction.Press(offset)
                    interactionSource.emit(press)

                    val released = tryAwaitRelease()

                    if (released) {
                        interactionSource.emit(PressInteraction.Release(press))
                        onClickState.value.invoke()
                    } else {
                        interactionSource.emit(PressInteraction.Cancel(press))
                    }
                },
            )
        }
    }

    this
        .then(semanticModifier)
        .then(pointerInputModifier)
}
z
Looking at the implementation of
performClick
, it looks like it doesn’t use the semantics action, it actually sends a click gesture, so i would think that should work. That said, you should still be setting the click action so accessibility services can interact with it.
Nothing in your code looks weird to me… have you tried putting a breakpoint in your pointerInput modifier to see if any touch events are getting through?
d
I just tried subbing out our own
fastClickable
for
.clickable { onToggle() }
and unfortunately the click still did not go through. Good to know about the accessibility services though, will forward that to our team 👍
Yes, the click seemingly does not go through checking breakpoints. I have one set in the parent composable and it never makes it to that callback, indicating that the
onToggle
never gets called
z
Is something else maybe intercepting events higher up in your composable hierarchy?
d
Hmm, I am not sure I understand how that would work. The composable directly above is where I set the breakpoint and I don't see any calls go through. Nothing above this class would obstruct the click from going through though.
z
Really weird. Can you share your code, or reproduce in something sharable? I’m not sure what else to suggest without actually stepping through all the test & input internals.
d
Sorry for the delay - I am currently debugging with some internal teams and was unsure if I had permissions to share our code, so if we are not able to fix it ourselves I can come up with an example to send over. Appreciate the help regardless
z
SG. If you do, might as well file a bug and post the code there (and please link to the issue here as well)