Skip to content

Add WidgetStatesController support to ExpansionTile#181238

Merged
auto-submit[bot] merged 9 commits into
flutter:masterfrom
iamvikashtiwari:improve-app-flavor-clean
Feb 25, 2026
Merged

Add WidgetStatesController support to ExpansionTile#181238
auto-submit[bot] merged 9 commits into
flutter:masterfrom
iamvikashtiwari:improve-app-flavor-clean

Conversation

@iamvikashtiwari

Copy link
Copy Markdown
Contributor

This PR adds support for providing a WidgetStatesController to ExpansionTile
and forwards it to the backing ListTile. This allows callers to observe and
control hovered, pressed, and focused states of the tile header.

The change includes updated documentation with a snippet example and adds
test coverage for both provided and null statesController cases.

Fixes #180161

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making.
  • I followed the breaking change policy and added Data Driven Fixes where supported.
  • All existing and new tests are passing.

@github-actions github-actions Bot added framework flutter/packages/flutter repository. See also f: labels. f: material design flutter/packages/flutter/material repository. labels Jan 21, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds WidgetStatesController support to ExpansionTile, which is a valuable enhancement for observing and controlling its interactive states. The implementation is straightforward and includes appropriate tests. My main feedback is on the example code provided in the documentation, which should be updated to correctly manage the lifecycle of the WidgetStatesController to prevent memory leaks and follow best practices.

Comment on lines +492 to +514
/// class ExpansionTileStatesExample extends StatelessWidget {
/// ExpansionTileStatesExample({super.key});
///
/// final WidgetStatesController controller = WidgetStatesController();
///
/// @override
/// Widget build(BuildContext context) {
/// controller.addListener(() {
/// if (controller.value.contains(WidgetState.hovered)) {
/// debugPrint('Tile is hovered');
/// }
/// });
///
/// return MaterialApp(
/// home: Scaffold(
/// body: ExpansionTile(
/// title: const Text('Settings'),
/// statesController: controller,
/// ),
/// ),
/// );
/// }
/// }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The example ExpansionTileStatesExample should be a StatefulWidget to properly manage the lifecycle of the WidgetStatesController. WidgetStatesController is a ChangeNotifier and needs to be disposed to avoid memory leaks. Currently, it's created in a StatelessWidget, which doesn't have a dispose method.

Additionally, adding a listener inside the build method can lead to adding the same listener multiple times, as build can be called repeatedly. The listener should be added in initState.

/// class ExpansionTileStatesExample extends StatefulWidget {
///   const ExpansionTileStatesExample({super.key});
///
///   @override
///   State<ExpansionTileStatesExample> createState() => _ExpansionTileStatesExampleState();
/// }
///
/// class _ExpansionTileStatesExampleState extends State<ExpansionTileStatesExample> {
///   late final WidgetStatesController _controller;
///
///   @override
///   void initState() {
///     super.initState();
///     _controller = WidgetStatesController();
///     _controller.addListener(() {
///       if (_controller.value.contains(WidgetState.hovered)) {
///         debugPrint('Tile is hovered');
///       }
///     });
///   }
///
///   @override
///   void dispose() {
///     _controller.dispose();
///     super.dispose();
///   }
///
///   @override
///   Widget build(BuildContext context) {
///     return MaterialApp(
///       home: Scaffold(
///         body: ExpansionTile(
///           title: const Text('Settings'),
///           statesController: _controller,
///         ),
///       ),
///     );
///   }
/// }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@iamvikashtiwari This looks like a real problem that should be addressed

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I missed this. For code snippet, maybe we should make it shorter? I didn't see a whole class example in code snippet previously. Maybe check the other use cases as examples.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like other WidgetStatesController fields don't provide a code example. However, it seems useful to have at least one example of this pattern.

I'd consider:

  1. Move this code example to be a full sample: examples/api/lib/widgets/widget_state/widget_states_controller.0.dart
  2. Add unit tests for this sample
  3. Add this code example to WidgetStatesController's docs directly using a {@tool dartpad} (instead of a {@tool snippet}).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gentle ping just in case this comment is missed:)!

@QuncCccccc QuncCccccc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might need to fix the failed tests and also the comments from the closed PR:) Thanks!

);
});

testWidgets('ExpansionTile forwards statesController to ListTile', (tester) async {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Run dart format packages/flutter/test/material/expansion_tile_test.dart to fix linux analyzer.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing this out!

I’ve fixed the formatting issue in
packages/flutter/test/material/expansion_tile_test.dart
by running dart format and pushed the update.

});

testWidgets('ExpansionTile forwards statesController to ListTile', (tester) async {
final controller = WidgetStatesController();

@QuncCccccc QuncCccccc Jan 28, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to dispose controller when we finish testing. Adding addTearDown(controller.dispose); after this line should fix the failed "Windows framework_tests_libraries_leak_tracking" test.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion!

Added addTearDown(controller.dispose); to properly dispose the
WidgetStatesController in the test. This should resolve the
Windows leak tracking failure.

expect(listTile.statesController, controller);
});

testWidgets('ExpansionTile forwards statesController to ListTile', (tester) async {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have 2 identical "ExpansionTile forwards statesController to ListTile" tests now. I think originally this is "ExpansionTile forwards null statesController to ListTile".

});

testWidgets('ExpansionTile forwards statesController to ListTile', (tester) async {
final controller = WidgetStatesController();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
final controller = WidgetStatesController();
final controller = WidgetStatesController();
addTearDown(controller.dispose);

// the default value to true.
final bool internalAddSemanticForOnTap;

/// {@tool snippet}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add a summary sentence so that IDEs like VS Code show useful code completion information if you type myExpansionTile.statesController. Consider something like:

Suggested change
/// {@tool snippet}
/// The controller that notifies when the widget's [WidgetState]s change.
///
/// {@tool snippet}

Comment thread packages/flutter/test/material/expansion_tile_test.dart
@QuncCccccc

Copy link
Copy Markdown
Contributor

Gentle ping for this comment here. If you think this is too much, I'm okay with removing the code snippet completely as well since I think this example looks simple.

@iamvikashtiwari

Copy link
Copy Markdown
Contributor Author

Thanks! Since the usage is straightforward, I’ve removed the snippet to keep the documentation concise.

@dkwingsmt dkwingsmt requested a review from QuncCccccc February 18, 2026 19:15

@QuncCccccc QuncCccccc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks!

@justinmc justinmc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍 . Always good to have more WidgetStatesController support.

@iamvikashtiwari

Copy link
Copy Markdown
Contributor Author

Thanks everyone for the reviews and guidance!

@QuncCccccc QuncCccccc added the autosubmit Merge PR when tree becomes green via auto submit App label Feb 20, 2026
@auto-submit auto-submit Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Feb 20, 2026
@auto-submit

auto-submit Bot commented Feb 20, 2026

Copy link
Copy Markdown
Contributor

autosubmit label was removed for flutter/flutter/181238, because The base commit of the PR is older than 7 days and can not be merged. Please merge the latest changes from the main into this branch and resubmit the PR.

@iamvikashtiwari iamvikashtiwari force-pushed the improve-app-flavor-clean branch from bbffc1f to 0824901 Compare February 25, 2026 10:37
@dkwingsmt dkwingsmt added the autosubmit Merge PR when tree becomes green via auto submit App label Feb 25, 2026
@auto-submit auto-submit Bot added this pull request to the merge queue Feb 25, 2026
Merged via the queue into flutter:master with commit 8af3c74 Feb 25, 2026
74 checks passed
@flutter-dashboard flutter-dashboard Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Feb 25, 2026
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Feb 26, 2026
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Feb 26, 2026
ahmedsameha1 pushed a commit to ahmedsameha1/flutter that referenced this pull request Feb 27, 2026
This PR adds support for providing a `WidgetStatesController` to
`ExpansionTile`
and forwards it to the backing `ListTile`. This allows callers to
observe and
control hovered, pressed, and focused states of the tile header.

The change includes updated documentation with a snippet example and
adds
test coverage for both provided and null `statesController` cases.

Fixes flutter#180161

## Pre-launch Checklist

- [x] I read the Contributor Guide and followed the process outlined
there for submitting PRs.
- [x] I read the Tree Hygiene wiki page, which explains my
responsibilities.
- [x] I read and followed the Flutter Style Guide, including Features we
expect every widget to implement.
- [x] I signed the CLA.
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making.
- [ ] I followed the breaking change policy and added Data Driven Fixes
where supported.
- [ ] All existing and new tests are passing.
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Feb 27, 2026
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Feb 27, 2026
xxxOVALxxx pushed a commit to xxxOVALxxx/flutter that referenced this pull request Mar 10, 2026
This PR adds support for providing a `WidgetStatesController` to
`ExpansionTile`
and forwards it to the backing `ListTile`. This allows callers to
observe and
control hovered, pressed, and focused states of the tile header.

The change includes updated documentation with a snippet example and
adds
test coverage for both provided and null `statesController` cases.

Fixes flutter#180161

## Pre-launch Checklist

- [x] I read the Contributor Guide and followed the process outlined
there for submitting PRs.
- [x] I read the Tree Hygiene wiki page, which explains my
responsibilities.
- [x] I read and followed the Flutter Style Guide, including Features we
expect every widget to implement.
- [x] I signed the CLA.
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making.
- [ ] I followed the breaking change policy and added Data Driven Fixes
where supported.
- [ ] All existing and new tests are passing.
mboetger pushed a commit to mboetger/flutter that referenced this pull request Mar 26, 2026
This PR adds support for providing a `WidgetStatesController` to
`ExpansionTile`
and forwards it to the backing `ListTile`. This allows callers to
observe and
control hovered, pressed, and focused states of the tile header.

The change includes updated documentation with a snippet example and
adds
test coverage for both provided and null `statesController` cases.

Fixes flutter#180161

## Pre-launch Checklist

- [x] I read the Contributor Guide and followed the process outlined
there for submitting PRs.
- [x] I read the Tree Hygiene wiki page, which explains my
responsibilities.
- [x] I read and followed the Flutter Style Guide, including Features we
expect every widget to implement.
- [x] I signed the CLA.
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making.
- [ ] I followed the breaking change policy and added Data Driven Fixes
where supported.
- [ ] All existing and new tests are passing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ExpansionTile should accept a WidgetStatesController for its backing ListTile

5 participants