Skip to content

Fix ExpansionTile semantics with WidgetSpan titles - #190639

Closed
ahamsel wants to merge 1 commit into
flutter:masterfrom
ahamsel:fix/expansion-tile-widgetspan-semantics
Closed

ahamsel wants to merge 1 commit into
flutter:masterfrom
ahamsel:fix/expansion-tile-widgetspan-semantics

Conversation

@ahamsel

@ahamsel ahamsel commented Aug 6, 2026 •

Copy link
Copy Markdown

Description

On Android, ExpansionTile used AccessibilityFocusBlockType.blockNode for its announcement live region without forcing that live region to form a semantics node. When an ExpansionTile was nested inside Text.rich and WidgetSpan, the blocked configuration could be merged through RenderParagraph's generated configurations and trigger the _IncompleteSemanticsFragment conflict assertion.

Make the live region a semantics container so blockNode applies to the intended node. This matches the existing blocked-live-region pattern used by CalendarDatePicker.

Fixes #188298.

Tests

  • flutter test --no-pub packages/flutter/test/material/expansion_tile_test.dart
  • flutter analyze --no-pub packages/flutter/lib/src/material/expansion_tile.dart packages/flutter/test/material/expansion_tile_test.dart

The author reviewed the AI-assisted changes. This remains a draft while the Material and Cupertino code freeze is active; the signed CLA check is also pending refresh.

Pre-launch Checklist

@google-cla

google-cla Bot commented Aug 6, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@navaronbracke

Copy link
Copy Markdown
Contributor

Hi @ahamsel

Since this is still targeting flutter/material, this will need to be ported to packages/material_ui

When I bumped into this issue myself, I also had a reproduction test case like below, which is a little simpler:

import 'package:material_ui/material_ui.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  testWidgets(
    'ExpansionTile inside a WidgetSpan inside a RichText throws a semantics assertion',
    (WidgetTester tester) async {
      await tester.pumpWidget(
        MaterialApp(
          home: Scaffold(
            body: Text.rich(
              TextSpan(
                children: <InlineSpan>[
                  WidgetSpan(
                    child: ExpansionTile(
                      title: Text('Header'),
                      children: <Widget>[Text('Details')],
                    ),
                  ),
                ],
              ),
            ),
          ),
        ),
      );

      // Never reached: pumpWidget above throws before returning, because
      // pumping a frame flushes semantics, which hits the assertion.
      expect(find.text('Header'), findsOneWidget);
    },
  );
}

Feel free to use this test instead when porting this fix to material_ui

@ahamsel

ahamsel commented Sep 5, 2026

Copy link
Copy Markdown
Author

Thanks, I moved the fix to flutter/packages#12765 and used your simpler example. I kept semantics enabled and the Android variant so the test catches the assertion.

I verified that the new test fails with the original !conflict assertion without container: true, then passes with it. The full ExpansionTile test file passes too (64 passed, one existing skip), and analysis and formatting are clean.

The earlier package PR could not be reopened after rebasing, so I linked the replacement to it.

auto-submit Bot pushed a commit to flutter/packages that referenced this pull request Oct 9, 2026
On Android, embedding an `ExpansionTile` in a `WidgetSpan` can trigger a sibling-configuration conflict assertion when semantics are enabled. Its live-region `Semantics` uses `AccessibilityFocusBlockType.blockNode` but can merge into the surrounding semantics tree.

This makes the live region a semantics container, so `blockNode` applies to an explicit node. The regression test uses the simpler reproduction suggested in flutter/flutter#190639 and relies on the semantics handle managed by `testWidgets`.

Fixes flutter/flutter#188298.

This ports flutter/flutter#190639 to `packages/material_ui`. The earlier package PR, #12383, was closed while contributions were paused. On October 2, I rebased this replacement onto current `main` (`10cde9a3b`) to refresh its stale CI configuration. The contribution itself is unchanged: the semantics fix, its regression test, and a pending changelog entry. There are no custom CI changes in this PR.

I used AI to help with the fix and regression test.

## Pre-Review Checklist

Local validation after the October 2 rebase, on macOS with the already-installed Flutter 3.47.5 / Dart 3.13.4:

- Full `material_ui` suite: `flutter test --no-pub --concurrency=2` — 8,222 passed, 12 existing skips, no failures.
- Full example suite: `flutter test --no-pub --concurrency=1` in `packages/material_ui/example` — 406 passed, two existing skips, no failures.
- ExpansionTile file separately: 64 passed, one existing skip (also included in the full suite).
- Defaults generator suite: `dart test` — 72 passed.
- Full-package `flutter analyze --no-pub`: no issues.
- Dart formatting and `git diff --check`: passed.
- `dart run script/tool/bin/flutter_plugin_tools.dart validate --packages material_ui --check-for-missing-changes --base-branch upstream/main`: passed.

I repeated the regression's fail-without-fix check on the rebased branch: removing `container: true` produces the original `!conflict` assertion; restoring it makes the test pass. The existing `flutter_goldens` harness skips golden-image comparisons; I did not change that behavior. These are local test results, not a claim that repository-wide CI has passed.

GitHub automatically removed the `CICD` label after the October 2 push. Could a maintainer reapply it for the refreshed head so the repository presubmits can run?

The release metadata uses `material_ui`'s batch-release convention: a pending changelog entry with `version: patch`.

**Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.

[^1]: Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling.
@ahamsel

ahamsel commented Oct 10, 2026

Copy link
Copy Markdown
Author

Closing this draft as superseded by flutter/packages#12765, which is now merged.

@ahamsel ahamsel closed this Oct 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

framework flutter/packages/flutter repository. See also f: labels. p: material_ui material_ui package in flutter/packages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ExpansionTile semantics assertion with nested WidgetSpan title in Flutter 3.44.2

2 participants