https://kotlinlang.org logo
Title
m

mattinger

01/13/2022, 10:04 PM
Hi All, so i’ve wrapped a RadioButton in a Row with some text. I then put the selectable modifier around that. I can now click anywhere in the row to select that radio button. However, when using the screenreader it initially announces it as i would like. But then if i swipe right, it’s individually selecting the radio button which i don’t want. Is there any way to disable that? (code in thread)
Row(
        modifier = modifier.selectable(
            selected = selected,
            onClick = onClick,
            enabled = enabled,
            role = role,
            interactionSource = interactionSource,
            indication = LocalIndication.current
        ),
        verticalAlignment = Alignment.CenterVertically
    ) {
       // Text and Radio Button
    }
I’ve tried adding .focusable(enabled=false) to the actual radio button itself, and that seems to have no effect.
z

Zach Klippenstein (he/him) [MOD]

01/13/2022, 10:32 PM
I think you want to merge semantics, so
Modifer.semantics(mergeDescendents=true)
c

Colton Idle

01/13/2022, 10:36 PM
Interesting. I would have thought the selectable modifier would merge automatically. I gotta go update some code this means! Good catch @mattinger
m

mattinger

01/13/2022, 11:53 PM
@Zach Klippenstein (he/him) [MOD] I tried adding the merge semantics to the Row above, and it doesn't seem to change anything
The tree is still showing them as separate semantics:
Printing with useUnmergedTree = 'false'
Node #1 at (l=0.0, t=56.0, r=41.0, b=93.0)px
 |-Node #2 at (l=0.0, t=56.0, r=41.0, b=93.0)px
   Role = 'RadioButton'
   Selected = 'false'
   Text = '[Label]'
   Actions = [OnClick, GetTextLayoutResult]
   MergeDescendants = 'true'
    |-Node #5 at (l=0.0, t=61.0, r=28.0, b=89.0)px, Tag: 'button1'
      Tone = 'Neutral'
      Role = 'RadioButton'
      Selected = 'false'
      Actions = [OnClick]
      MergeDescendants = 'true'
I find it strange that even though i deliberately set the mergeDescendants flag on the radio button itself to "false" it's coming back as true. Is there something about the "selectable" modifier that maybe does that?
looks like through a series of calls, it’s ending up calling:
genericClickableWithoutGesture
when you use .selectable
that is setting the merge flag to true on the radio button
and from what i can tell from the documentation, it won’t merge nodes that both have mergeDecendants set to true
z

Zach Klippenstein (he/him) [MOD]

01/14/2022, 12:25 AM
Hm, good point.
@Anastasia [G] Do you know what the right thing to do is here?
a

Albert Chang

01/14/2022, 12:48 AM
Are you specifying the
onClick
parameter of
RadioButton
? If so, try setting it to null.
m

mattinger

01/14/2022, 1:20 AM
let me try that.
that’s perfect @Albert Chang. I’ll have to see if i can make the same trick work with switch and checkbox (we’re doing this with all 3 of them)
So it worked with switch, but checkboxes are wierd. It announces as: Not Checked, Not Checked, Label Checkbox, Double Tap to Toggle
c

Colton Idle

01/14/2022, 1:26 AM
Oh nice. No need for mergeDescendants? Just onClick = null?
m

mattinger

01/14/2022, 1:27 AM
Yes. Other than the checkbox which is announcing Not checked twice.
c

Colton Idle

01/14/2022, 1:28 AM
Seems like maybe thats worth filing a bug report.
m

mattinger

01/14/2022, 1:29 AM
It’s possible because we’re doing a triStateToggleable for the checkbox. Checkboxes on their own announce fine.
c

Colton Idle

01/14/2022, 1:33 AM
There's a tristate toggleable for a checkbox? I always thought there were two states. checked and unchecked. :mind-blown:
m

mattinger

01/14/2022, 1:44 AM
@Composable
fun TriStateCheckbox(
    state: ToggleableState,
    onClick: (() -> Unit)?,
    modifier: Modifier = Modifier,
    enabled: Boolean = true,
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
    colors: CheckboxColors = CheckboxDefaults.colors()
)
it has an indeteriminate state which puts a “-” in there. It’s good for hierarchal things where some underneath it may be checked and some not.
we’ve just based our checkbox off of that, but it seems like nesting these things is having issues.
I’ve even validated that a null is being passed to the triStateToggleable in the underlying code:
Modifier.selectable(
                selected = selected,
                onClick = onClick,
                enabled = enabled,
                role = Role.RadioButton,
                interactionSource = interactionSource,
                indication = rememberRipple(
                    bounded = false,
                    radius = RadioButtonRippleRadius
                )
            )
Switch is still an interesting case, because you can no longer swipe it to change it’s state. you can only tap
a

Albert Chang

01/14/2022, 2:15 AM
Yeah that's expected. You can check the expected behavior in the system setting app.
m

mattinger

01/14/2022, 2:25 AM
I figured out the checkbox thing. we add some additional semantic properties and an overzealous dev added role and toggleableState when they’re already taken care of in the TriStateCheckbox.
a

Anastasia [G]

01/14/2022, 3:00 PM
The suggestion above to pass
null
is right, this is exactly why we made the callback nullable. We keep https://issuetracker.google.com/issues/193414941 open to improve the API reference documentation, maybe a sample of this use case closer to the
RadioButton
documentation itself would help
m

mattinger

01/15/2022, 12:10 AM
Thanks @Anastasia [G] for the link.