From 380c8b4a657503170f2f2629346e7cd22b2fdcaf Mon Sep 17 00:00:00 2001 From: Kostia Sokolovskyi Date: Fri, 23 Jan 2026 00:16:57 +0100 Subject: [PATCH] Add ExpansibleController.toggle method. (#181320) Closes https://github.com/flutter/flutter/issues/181319 ### Description - Adds `ExpansibleController.toggle` method - Adds test to verify that `ExpansibleController.toggle` works as expected ## 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, or this PR is [test-exempt]. - [X] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [X] All existing and new tests are passing. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --------- Co-authored-by: Victor Sanni --- .../flutter/lib/src/widgets/expansible.dart | 15 ++++++++++ .../flutter/test/widgets/expansible_test.dart | 30 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/packages/flutter/lib/src/widgets/expansible.dart b/packages/flutter/lib/src/widgets/expansible.dart index 356ee0940a6..41f7c7a07ce 100644 --- a/packages/flutter/lib/src/widgets/expansible.dart +++ b/packages/flutter/lib/src/widgets/expansible.dart @@ -126,6 +126,21 @@ class ExpansibleController extends ChangeNotifier { _setExpansionState(false); } + /// Convenience method for toggling the current [isExpanded] status. + /// + /// Calling this method may cause the [Expansible] to rebuild, so it may not + /// be called from a build method. + /// + /// Calling this method will notify registered listeners of this controller + /// that the expansion state has changed. + /// + /// See also: + /// + /// * [expand], which expands the [Expansible]. + /// * [collapse], which collapses the [Expansible]. + /// * [isExpanded] to check whether the [Expansible] is expanded. + void toggle() => isExpanded ? collapse() : expand(); + /// Finds the [ExpansibleController] for the closest [Expansible] instance /// that encloses the given context. /// diff --git a/packages/flutter/test/widgets/expansible_test.dart b/packages/flutter/test/widgets/expansible_test.dart index c9c03fbb9ad..4fa2c6394c3 100644 --- a/packages/flutter/test/widgets/expansible_test.dart +++ b/packages/flutter/test/widgets/expansible_test.dart @@ -511,4 +511,34 @@ void main() { controller.expand(); await tester.pump(); }); + + testWidgets('Controller can be toggled', (WidgetTester tester) async { + final controller = ExpansibleController(); + addTearDown(controller.dispose); + + await tester.pumpWidget( + MaterialApp( + home: Expansible( + controller: controller, + bodyBuilder: (BuildContext context, Animation animation) => const Text('Body'), + headerBuilder: (BuildContext context, Animation animation) => + GestureDetector(onTap: controller.toggle, child: const Text('Header')), + ), + ), + ); + + expect(find.text('Body'), findsNothing); + controller.toggle(); + await tester.pumpAndSettle(); + expect(find.text('Body'), findsOneWidget); + + controller.toggle(); + await tester.pumpAndSettle(); + expect(find.text('Body'), findsNothing); + + expect(find.text('Body'), findsNothing); + controller.toggle(); + await tester.pumpAndSettle(); + expect(find.text('Body'), findsOneWidget); + }); }