Skip to content

Android TalkBack announces previous radio button's unchecked state when tapping a new radio button wrapped in Feedback.wrapForTap #166448

@ash2moon

Description

@ash2moon

Platform: Android (with TalkBack enabled)

Context:

This issue was observed while refactoring the Material Time Picker's AM/PM selector (packages/flutter/lib/src/material/time_picker.dart). The goal of the refactoring is to move away from using SemanticService.announce and instead rely on a more idiomatic semantic widget tree that self-describes the state changes. Custom radio buttons using Feedback.wrapForTap were implemented for the AM/PM selection.
#165510

Problem Description:

When using a group of custom radio buttons where each button is wrapped in Feedback.wrapForTap, tapping on a button (other than the first one selected) causes the TalkBack screen reader on Android to announce the state change of the previously selected button (checked/not checked) after announcing the state change of the currently tapped button (becoming checked/unchecked).

Steps to Reproduce:

  1. Enable the TalkBack screen reader on an Android device.
  2. Run the minimal reproduction code provided below.
  3. Go to "Item 1" radio button. TalkBack announces its selection correctly (e.g., "not checked, Item 1, Radio").
  4. Tap the "Item 1" radio button. TalkBack announces its selection correctly (e.g., "checked").
  5. Go to "Item 2" radio button. TalkBack announces its selection correctly (e.g., "not checked, Item 2, Radio").
  6. Tap the "Item 2" radio button. TalkBack does not announcesits selection correctly (e.g., "checked, not checked").

Expected Results:

When tapping "Item 2", TalkBack should only announce the state of the tapped element (e.g, "checked")

The state change of the previously selected element ("Item 1") should not be announced as part of the interaction with "Item 2".

Actual Results:

When tapping "Item 2" after "Item 1" was selected, TalkBack announces the state change of both elements:

"checked, not checked"

Video:

screen-20250402-113102.2.mp4

Minimal Reproduction Code:

import 'package:flutter/material.dart';

void main() {
  runApp(App(child: Radio()));
}

class App extends StatelessWidget {
  final Widget child;

  const App({
    super.key,
    required this.child,
  });

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Radio Sample')),
        body: Center(child: child),
      ),
    );
  }
}

class Radio extends StatefulWidget {
  const Radio({super.key});

  @override
  State<Radio> createState() => _RadioState();
}

class _RadioState extends State<Radio> {
  var selected = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Semantics(
          checked: selected == 1,
          button: true,
          inMutuallyExclusiveGroup: true,
          child: InkWell(
            onTap: Feedback.wrapForTap(() {
              setState(() {
                selected = 1;
              });
            }, context),
            child: Text("${selected == 1 ? "X " : ""}Item 1"),
          ),
        ),
        Semantics(
          checked: selected == 2,
          button: true,
          inMutuallyExclusiveGroup: true,
          child: InkWell(
            onTap: Feedback.wrapForTap(() {
              setState(() {
                selected = 2;
              });
            }, context),
            child: Text("${selected == 2 ? "X " : ""}Item 2"),
          ),
        ),
        Semantics(
          checked: selected == 3,
          button: true,
          inMutuallyExclusiveGroup: true,
          child: InkWell(
            onTap: Feedback.wrapForTap(() {
              setState(() {
                selected = 3;
              });
            }, context),
            child: Text("${selected == 3 ? "X " : ""}Item 3"),
          ),
        ),
      ],
    );
  }
}

Metadata

Metadata

Assignees

Labels

P1High-priority issues at the top of the work lista: accessibilityAccessibility, e.g. VoiceOver or TalkBack. (aka a11y)platform-androidAndroid applications specificallyteam-androidOwned by Android platform teamtriaged-androidTriaged by Android platform team

Type

Projects

Status

Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions