Skip to content

Talkback & VoiceOver Do not update custom semantics action after an action has been performed #149613

Closed

Description

@zywek123

Steps to reproduce

Run the code provided
Turn on voiceOver or Talkback
Swipe up or down to hear "Increment 0"
Swipe up or down back to "Activate" or left and right to hear "increment button"
Double click the increment button
Swipe up or down at the Increment button to hear "Increment Number"

Expected results

VoiceOver and Talkback should say Increment 1, Increment 2 and so on when You will again swipe up or down at the increment button after each click the button.

Actual results

VoiceOver and Talkback always say Increment 0 when you swipe up or down to choose action. When you touch the screen two fingers on Android to show Actions, You will see Increment 0 instead of Increment CurrentValue

Code sample

Code sample
import 'package:flutter/material.dart';
import 'package:flutter/semantics.dart';
void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  void _incrementCounter({int? num}) {
    setState(() {
      _counter += num??1;
    });
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
		  ],
        ),
      ),
      floatingActionButton: Semantics(excludeSemantics: true, // avoiding duplicate controls
 button: true, // emitsVoiceOver button information
customSemanticsActions: {CustomSemanticsAction(label: "Increment  $_counter"): () => _incrementCounter(num: _counter)}, // should increment a value of _counter with previous _counter after choosing action by swiping up or down
label: 'increment', // a VoiceOver button's label added here because of Excluding Semantics
 child: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      )), 
    );
  }
}

Screenshots or Video

No response

Logs

Doctor output channel master
 Flutter (Channel master, 3.23.0-13.0.pre.79, on macOS 13.6.6 22G630 darwin-arm64, locale pl-PL)
    • Flutter version 3.23.0-13.0.pre.79 on channel master at /Users/arkadiuszkoziol/git/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision 691a18df5f (55 minutes ago), 2024-06-03 12:12:11 -0700
    • Engine revision ea72558be7
    • Dart version 3.5.0 (build 3.5.0-214.0.dev)
    • DevTools version 2.36.0

[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
    • Android SDK at /Users/arkadiuszkoziol/Library/Android/sdk
    • Platform android-34, build-tools 34.0.0
    • Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 17.0.6+0-17.0.6b829.9-10027231)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 15.2)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 15C500b
    • CocoaPods version 1.15.2

[✗] Chrome - develop for the web (Cannot find Chrome executable at /Applications/Brave\ Browser.app/Contents/MacOS/Brave\ Browser)
    ! /Applications/Brave\ Browser.app/Contents/MacOS/Brave\ Browser is not executable.

[✓] Android Studio (version 2022.3)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 17.0.6+0-17.0.6b829.9-10027231)

[✓] Connected device (3 available)
    • iPhone (arek) (mobile)          • 130b7ef6202e72e09d74b84afd1a1cd5f82d57dc • ios          • iOS 16.7.7 20H330
    • macOS (desktop)                 • macos                                    • darwin-arm64 • macOS 13.6.6 22G630 darwin-arm64
    • Mac Designed for iPad (desktop) • mac-designed-for-ipad                    • darwin       • macOS 13.6.6 22G630 darwin-arm64

[✓] Network resources
    • All expected network resources are available.

! Doctor found issues in 1 category.

Flutter Doctor output

Doctor output channel stable
Flutter (Channel stable, 3.22.1, on Arch Linux 6.9.2-arch1-1, locale pl_PL.UTF-8)
    • Flutter version 3.22.1 on channel stable at /opt/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision a14f74ff3a (12 dni temu), 2024-05-22 11:08:21 -0500
    • Engine revision 55eae6864b
    • Dart version 3.4.1
    • DevTools version 2.34.3

[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
    • Android SDK at /opt/android-sdk
    • Platform android-VanillaIceCream, build-tools 34.0.0
    • ANDROID_HOME = /opt/android-sdk
    • ANDROID_SDK_ROOT = /opt/android-sdk
    • Java binary at: /usr/bin/java
    • Java version OpenJDK Runtime Environment (build 17.0.11+9)
    • All Android licenses accepted.

[✓] Chrome - develop for the web
    • CHROME_EXECUTABLE = /opt/brave-bin/brave

[✓] Linux toolchain - develop for Linux desktop
    • clang version 17.0.6
    • cmake version 3.29.3
    • ninja version 1.12.1
    • pkg-config version 2.1.1

[!] Android Studio (not installed)
    • Android Studio not found; download from https://developer.android.com/studio/index.html
      (or visit https://flutter.dev/docs/get-started/install/linux#android-setup for detailed instructions).

[✓] Connected device (3 available)
    • SM S901B (mobile) • R3CT40BG5XE • android-arm64  • Android 14 (API 34)
    • Linux (desktop)   • linux       • linux-x64      • Arch Linux 6.9.2-arch1-1
    • Chrome (web)      • chrome      • web-javascript • Brave Browser 125.1.66.115 unknown

[✓] Network resources
    • All expected network resources are available.

! Doctor found issues in 1 category.

Activity

  1. changed the title [-]Talkback & VoiceOver Do n'ot update custom semantis action after an action has been performed[/-] [+]Talkback & VoiceOver Do not update custom semantis action after an action has been performed[/+] on Jun 4, 2024
  2. darshankawar commented on Jun 4, 2024

    @darshankawar
    Member

    Thanks for the report. I was able to replicate this on latest versions and occurs in both platforms.

    stable, master flutter doctor -v
    [!] Flutter (Channel stable, 3.22.1, on macOS 12.2.1 21D62 darwin-x64, locale
        en-GB)
        • Flutter version 3.22.1 on channel stable at
          /Users/dhs/documents/fluttersdk/flutter
        ! Warning: `flutter` on your path resolves to
          /Users/dhs/Documents/Fluttersdk/flutter/bin/flutter, which is not inside
          your current Flutter SDK checkout at
          /Users/dhs/documents/fluttersdk/flutter. Consider adding
          /Users/dhs/documents/fluttersdk/flutter/bin to the front of your path.
        ! Warning: `dart` on your path resolves to
          /Users/dhs/Documents/Fluttersdk/flutter/bin/dart, which is not inside your
          current Flutter SDK checkout at /Users/dhs/documents/fluttersdk/flutter.
          Consider adding /Users/dhs/documents/fluttersdk/flutter/bin to the front
          of your path.
        • Upstream repository https://github.com/flutter/flutter.git
        • Framework revision a14f74ff3a (12 days ago), 2024-05-22 11:08:21 -0500
        • Engine revision 55eae6864b
        • Dart version 3.4.1
        • DevTools version 2.34.3
        • If those were intentional, you can disregard the above warnings; however
          it is recommended to use "git" directly to perform update checks and
          upgrades.
    
    [!] Xcode - develop for iOS and macOS (Xcode 12.3)
        • Xcode at /Applications/Xcode.app/Contents/Developer
        ! Flutter recommends a minimum Xcode version of 13.
          Download the latest version or update via the Mac App Store.
        • CocoaPods version 1.11.2
    
    [✓] Chrome - develop for the web
        • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
    
    [✓] VS Code (version 1.62.0)
        • VS Code at /Applications/Visual Studio Code.app/Contents
        • Flutter extension version 3.21.0
    
    [✓] Connected device (5 available)
        • SM G975F (mobile)       • RZ8M802WY0X • android-arm64   • Android 11 (API 30)
        • Darshan's iphone (mobile)  • 21150b119064aecc249dfcfe05e259197461ce23 •
          ios            • iOS 14.4.1 18D61
        • iPhone 12 Pro Max (mobile) • A5473606-0213-4FD8-BA16-553433949729     •
          ios            • com.apple.CoreSimulator.SimRuntime.iOS-14-3 (simulator)
        • macOS (desktop)            • macos                                    •
          darwin-x64     • Mac OS X 10.15.4 19E2269 darwin-x64
        • Chrome (web)               • chrome                                   •
          web-javascript • Google Chrome 98.0.4758.80
    
    [✓] HTTP Host Availability
        • All required HTTP hosts are available
    
    ! Doctor found issues in 1 category.
    
    [!] Flutter (Channel master, 3.23.0-13.0.pre.69, on macOS 12.2.1 21D62
        darwin-x64, locale en-GB)
        • Flutter version 3.23.0-13.0.pre.69 on channel master at
          /Users/dhs/documents/fluttersdk/flutter
        ! Warning: `flutter` on your path resolves to
          /Users/dhs/Documents/Fluttersdk/flutter/bin/flutter, which is not inside
          your current Flutter SDK checkout at
          /Users/dhs/documents/fluttersdk/flutter. Consider adding
          /Users/dhs/documents/fluttersdk/flutter/bin to the front of your path.
        ! Warning: `dart` on your path resolves to
          /Users/dhs/Documents/Fluttersdk/flutter/bin/dart, which is not inside your
          current Flutter SDK checkout at /Users/dhs/documents/fluttersdk/flutter.
          Consider adding /Users/dhs/documents/fluttersdk/flutter/bin to the front
          of your path.
        • Upstream repository https://github.com/flutter/flutter.git
        • Framework revision 124aacbaef (2 days ago), 2024-05-31 22:32:25 -0400
        • Engine revision d81edf635a
        • Dart version 3.5.0 (build 3.5.0-214.0.dev)
        • DevTools version 2.36.0
        • If those were intentional, you can disregard the above warnings; however
          it is recommended to use "git" directly to perform update checks and
          upgrades.
    
    [!] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
        • Android SDK at /Users/dhs/Library/Android/sdk
        ✗ cmdline-tools component is missing
          Run `path/to/sdkmanager --install "cmdline-tools;latest"`
          See https://developer.android.com/studio/command-line for more details.
        ✗ Android license status unknown.
          Run `flutter doctor --android-licenses` to accept the SDK licenses.
          See https://flutter.dev/docs/get-started/install/macos#android-setup for
          more details.
    
    [✓] Xcode - develop for iOS and macOS (Xcode 13.2.1)
        • Xcode at /Applications/Xcode.app/Contents/Developer
        • Build 13C100
        • CocoaPods version 1.11.2
    
    [✓] Chrome - develop for the web
        • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
    
    [✓] IntelliJ IDEA Ultimate Edition (version 2021.3.2)
        • IntelliJ at /Applications/IntelliJ IDEA.app
        • Flutter plugin version 65.1.4
        • Dart plugin version 213.7228
    
    [✓] VS Code (version 1.62.0)
        • VS Code at /Applications/Visual Studio Code.app/Contents
        • Flutter extension version 3.29.0
    
    [✓] Connected device (3 available)
        • Darshan's iphone (mobile) • 21150b119064aecc249dfcfe05e259197461ce23 • ios
          • iOS 15.3.1 19D52
        • macOS (desktop)           • macos                                    •
          darwin-x64     • macOS 12.2.1 21D62 darwin-x64
        • Chrome (web)              • chrome                                   •
          web-javascript • Google Chrome 109.0.5414.119
    
    [✓] Network resources
        • All expected network resources are available.
    
    ! Doctor found issues in 1 category.
          
    [!] Xcode - develop for iOS and macOS (Xcode 12.3)
        • Xcode at /Applications/Xcode.app/Contents/Developer
        ! Flutter recommends a minimum Xcode version of 13.
          Download the latest version or update via the Mac App Store.
        • CocoaPods version 1.11.2
    
    [✓] Chrome - develop for the web
        • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
    
    [✓] VS Code (version 1.62.0)
        • VS Code at /Applications/Visual Studio Code.app/Contents
        • Flutter extension version 3.21.0
    
    [✓] Connected device (5 available)
        • SM G975F (mobile)       • RZ8M802WY0X • android-arm64   • Android 11 (API 30)
        • Darshan's iphone (mobile)  • 21150b119064aecc249dfcfe05e259197461ce23 •
          ios            • iOS 14.4.1 18D61
        • iPhone 12 Pro Max (mobile) • A5473606-0213-4FD8-BA16-553433949729     •
          ios            • com.apple.CoreSimulator.SimRuntime.iOS-14-3 (simulator)
        • macOS (desktop)            • macos                                    •
          darwin-x64     • Mac OS X 10.15.4 19E2269 darwin-x64
        • Chrome (web)               • chrome                                   •
          web-javascript • Google Chrome 98.0.4758.80
    
    [✓] HTTP Host Availability
        • All required HTTP hosts are available
    
    ! Doctor found issues in 1 category.
    
    
    
    
  3. added
    platform-androidAndroid applications specifically
    platform-iosiOS applications specifically
    a: accessibilityAccessibility, e.g. VoiceOver or TalkBack. (aka a11y)
    has reproducible stepsThe issue has been confirmed reproducible and is ready to work on
    frameworkflutter/packages/flutter repository. See also f: labels.
    and removed
    in triagePresently being triaged by the triage team
    on Jun 4, 2024
  4. changed the title [-]Talkback & VoiceOver Do not update custom semantis action after an action has been performed[/-] [+]Talkback & VoiceOver Do not update custom semantics action after an action has been performed[/+] on Jun 5, 2024
  5. added
    P2Important issues not at the top of the work list
    on Jun 5, 2024
  6. s17476 commented on Jul 31, 2024

    @s17476

    Hey @zywek123,

    Thanks for contacting me via email. I also had this problem in my app. The Semantics widget does not update after receiving new customSemanticsActions, although it should.

    The workaround is trivially simple. Just use a label in Semantics widget and update it as the value that the list of actions depends on changes.

    I use a single dot for this😅 It does not change the content of the message, but forces the widget to update.

    Below you will find your improved example correctly reading labels in the action list. I simplified the increment method a bit to make it easier to detect the change in the counter value.

    import 'package:flutter/material.dart';
    import 'package:flutter/semantics.dart';
    
    void main() {
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
            useMaterial3: true,
          ),
          home: const MyHomePage(title: 'Flutter Demo Home Page'),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      const MyHomePage({super.key, required this.title});
    
      final String title;
    
      @override
      State<MyHomePage> createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      int _counter = 0;
    
      void _incrementCounter() {
        setState(() {
          _counter++;
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            backgroundColor: Theme.of(context).colorScheme.inversePrimary,
            title: Text(widget.title),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                const Text(
                  'You have pushed the button this many times:',
                ),
                Text(
                  '$_counter',
                  style: Theme.of(context).textTheme.headlineMedium,
                ),
              ],
            ),
          ),
          floatingActionButton: Semantics(
            excludeSemantics: true,
            button: true,
            customSemanticsActions: {
              CustomSemanticsAction(label: "Increment  $_counter"):
                  _incrementCounter
            },
            // Changing label will force Semantics widget to update
            label: 'increment${_counter.isEven ? '.' : ''}',
            child: FloatingActionButton(
              onPressed: _incrementCounter,
              tooltip: 'Increment',
              child: const Icon(Icons.add),
            ),
          ),
        );
      }
    }

    In my app I do it like this:

    I use helper class + extension + methods

    import 'package:flutter/material.dart';
    import 'package:flutter/semantics.dart';
    
    class ContextMenuItem {
      final String label;
      final VoidCallback callback;
    
      ContextMenuItem({
        required this.label,
        required this.callback,
      });
    }
    
    extension ContextMenuList on List<ContextMenuItem> {
      /// If any custom actions have already been added,
      /// overwriting this map with an empty one or null
      /// does not remove them in the Semantics widget.
      /// You need to overwrite the default action.
      Map<CustomSemanticsAction, VoidCallback>? toCustomSemanticAction({
        bool overrideDefaultAction = false,
      }) {
        Map<CustomSemanticsAction, VoidCallback> actions = {};
    
        for (var item in this) {
          actions[CustomSemanticsAction(label: item.label)] = item.callback;
        }
    
        if (actions.isEmpty) {
          if (overrideDefaultAction) {
            return {
              const CustomSemanticsAction.overridingAction(
                hint: 'Activate',
                action: SemanticsAction.tap,
              ): () {}
            };
          } else {
            return null;
          }
        }
    
        return actions;
      }
    
      List<PopupMenuEntry>? toPopupMenuItems() {
        List<PopupMenuEntry> items = [];
    
        for (var item in this) {
          items.add(PopupMenuItem(onTap: item.callback, child: Text(item.label)));
        }
    
        if (items.isEmpty) return null;
    
        return items;
      }
    }
    
    void showContextMenu(BuildContext context, List<ContextMenuItem>? items) {
      if (items != null && items.isNotEmpty) {
        if (items.length == 1) {
          items.first.callback.call();
        } else {
          showMenu(
            context: context,
            position: const RelativeRect.fromLTRB(0, 0, 0, 0),
            items: items.toPopupMenuItems()!,
          );
        }
      }
    }

    Then you can easily use the list of these actions as a context menu or popup menu - I had feedback from TalkBack users that due to set preferences they didn't always have access to the context menu, so I always add a popup menu as well.

    import 'package:dynamic_custom_semantics_actions/context_menu_item.dart';
    import 'package:flutter/material.dart';
    import 'package:flutter/rendering.dart';
    
    void main() {
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'CustomSemanticsActions',
          theme: ThemeData(
            colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
            useMaterial3: true,
          ),
          home: const MyHomePage(),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      const MyHomePage({super.key});
    
      @override
      State<MyHomePage> createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      bool _optionOneEnabled = false;
      bool _optionTwoEnabled = true;
    
      List<ContextMenuItem> _createContextMenuOne() => [
            if (_optionOneEnabled)
              ContextMenuItem(
                label: 'Disable',
                callback: () {
                  setState(() {
                    _optionOneEnabled = false;
                  });
    
                  Future.delayed(
                    Durations.medium2,
                    () => SemanticsService.announce('Disabled', TextDirection.ltr),
                  );
                },
              )
            else
              ContextMenuItem(
                label: 'Enable',
                callback: () {
                  setState(() {
                    _optionOneEnabled = true;
                  });
    
                  Future.delayed(
                    Durations.medium2,
                    () => SemanticsService.announce('Enabled', TextDirection.ltr),
                  );
                },
              )
          ];
    
      List<ContextMenuItem> _createContextMenuTwo() => [
            if (_optionTwoEnabled)
              ContextMenuItem(
                label: 'Hide',
                callback: () {
                  setState(() {
                    _optionTwoEnabled = false;
                  });
    
                  Future.delayed(
                    Durations.medium2,
                    () => SemanticsService.announce('Hidden', TextDirection.ltr),
                  );
                },
              )
          ];
    
      @override
      Widget build(BuildContext context) {
        final contextMenuOneItems = _createContextMenuOne();
        final contextMenuTwoItems = _createContextMenuTwo();
    
        // This does the trick.
        // Semantics widget will update custom semantics actions,
        // because its label has changed. I'm used to add a dot '.' here.
        final labelOne = 'First label${_optionOneEnabled ? '.' : ''}';
        final labelTwo = 'Second label${_optionTwoEnabled ? '.' : ''}';
    
        return Scaffold(
          appBar: AppBar(
            backgroundColor: Theme.of(context).colorScheme.inversePrimary,
            title: const Text('CustomSemanticsActions'),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                Semantics(
                  label: labelOne,
                  customSemanticsActions:
                      contextMenuOneItems.toCustomSemanticAction(),
                  child: InkWell(
                    onLongPress: () =>
                        showContextMenu(context, contextMenuOneItems),
                    child: const ExcludeSemantics(
                      child: Text('First widget'),
                    ),
                  ),
                ),
                Semantics(
                  label: labelTwo,
                  customSemanticsActions: contextMenuTwoItems
                      .toCustomSemanticAction(overrideDefaultAction: true),
                  child: InkWell(
                    onLongPress: () =>
                        showContextMenu(context, contextMenuTwoItems),
                    child: const ExcludeSemantics(
                      child: Text('Second widget'),
                    ),
                  ),
                ),
              ],
            ),
          ),
        );
      }
    }

    I've only tested this with VoiceOver, but in our production app it works just as well with TalkBack.

    Code also available here: https://github.com/s17476/dynamic_custom_semantics_actions

  7. github-actions commented on May 14, 2026

    @github-actions

    This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

  8. locked as resolved and limited conversation to collaborators on May 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work lista: accessibilityAccessibility, e.g. VoiceOver or TalkBack. (aka a11y)found in release: 3.22Found to occur in 3.22found in release: 3.23Found to occur in 3.23frameworkflutter/packages/flutter repository. See also f: labels.has reproducible stepsThe issue has been confirmed reproducible and is ready to work onplatform-androidAndroid applications specificallyplatform-iosiOS applications specificallyteam-frameworkOwned by Framework teamtriaged-frameworkTriaged by Framework team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions