Skip to content

Commit 5047690

Browse files
authored
ExpansionPanel isExpanded callback parameter (Ticket 74114) (#128082)
Fixes flutter/flutter#74114 This PR addresses the issue detailed here: flutter/flutter#74114 . The boolean isExpanded returned by the expansion panel callback now reflects the state of the panel that the user is seeing. If it's expanded on screen then the callback returns true. When you close the panel the callback returns false. When another panel is open and you open a different one, the callback executes twice. It returns isExpanded == false for the panel you are closing and true for the panel that is being opened. I had to change the code in a couple existing tests because some tests are using the old behavior of the callback. This PR addresses feedback listed in closed PR -> flutter/flutter#127876 . The reasone the original PR is closed is that I was having some struggles with git. A couple of the commits in this PR are just reverts of commits I meant not to happen. Pre-launch Checklist [ X] I read the [Contributor Guide](https://github.com/flutter/flutter/wiki/Tree-hygiene#overview) and followed the process outlined there for submitting PRs. [ X] I read the [Tree Hygiene](https://github.com/flutter/flutter/wiki/Tree-hygiene) wiki page, which explains my responsibilities. [ X] I read and followed the [Flutter Style Guide](https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo), including [Features we expect every widget to implement](https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#features-we-expect-every-widget-to-implement). [ X] I signed the [CLA](https://cla.developers.google.com/). [ X] I listed at least one issue that this PR fixes in the description above. 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](https://github.com/flutter/flutter/wiki/Tree-hygiene#tests). [ X] All existing and new tests are passing.
1 parent 38406ff commit 5047690

File tree

3 files changed

+106
-11
lines changed

3 files changed

+106
-11
lines changed

dev/integration_tests/flutter_gallery/lib/demo/material/expansion_panels_demo.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ class _ExpansionPanelsDemoState extends State<ExpansionPanelsDemo> {
345345
child: ExpansionPanelList(
346346
expansionCallback: (int index, bool isExpanded) {
347347
setState(() {
348-
_demoItems[index].isExpanded = !isExpanded;
348+
_demoItems[index].isExpanded = isExpanded;
349349
});
350350
},
351351
children: _demoItems.map<ExpansionPanel>((DemoItem<dynamic> item) {

packages/flutter/lib/src/material/expansion_panel.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -313,9 +313,7 @@ class _ExpansionPanelListState extends State<ExpansionPanelList> {
313313
return widget.children[index].isExpanded;
314314
}
315315

316-
void _handlePressed(bool isExpanded, int index) {
317-
widget.expansionCallback?.call(index, isExpanded);
318-
316+
void _handlePressed(bool isExpanded, int index) {
319317
if (widget._allowOnlyOnePanelOpen) {
320318
final ExpansionPanelRadio pressedChild = widget.children[index] as ExpansionPanelRadio;
321319

@@ -334,6 +332,8 @@ class _ExpansionPanelListState extends State<ExpansionPanelList> {
334332
_currentOpenPanel = isExpanded ? null : pressedChild;
335333
});
336334
}
335+
// !isExpanded is passed because, when _handlePressed, the state of the panel to expand is not yet expanded.
336+
widget.expansionCallback?.call(index, !isExpanded);
337337
}
338338

339339
ExpansionPanelRadio? searchPanelByValue(List<ExpansionPanelRadio> panels, Object? value) {

packages/flutter/test/material/expansion_panel_test.dart

Lines changed: 102 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ void main() {
144144
expect(find.byType(ExpandIcon), findsOneWidget);
145145
await tester.tap(find.byType(ExpandIcon));
146146
expect(capturedIndex, 0);
147-
expect(capturedIsExpanded, isFalse);
147+
expect(capturedIsExpanded, isTrue);
148148
box = tester.renderObject(find.byType(ExpansionPanelList));
149149
expect(box.size.height, equals(oldHeight));
150150

@@ -556,7 +556,7 @@ void main() {
556556
// Callback is invoked once with appropriate arguments
557557
expect(callbackHistory.length, equals(1));
558558
expect(callbackHistory.last['index'], equals(1));
559-
expect(callbackHistory.last['isExpanded'], equals(false));
559+
expect(callbackHistory.last['isExpanded'], equals(true));
560560

561561
// Close the same panel
562562
await tester.tap(find.byType(ExpandIcon).at(1));
@@ -565,7 +565,7 @@ void main() {
565565
// Callback is invoked once with appropriate arguments
566566
expect(callbackHistory.length, equals(2));
567567
expect(callbackHistory.last['index'], equals(1));
568-
expect(callbackHistory.last['isExpanded'], equals(true));
568+
expect(callbackHistory.last['isExpanded'], equals(false));
569569
});
570570

571571
testWidgets('Radio mode calls expansionCallback twice if other panel open prior', (WidgetTester tester) async {
@@ -630,7 +630,7 @@ void main() {
630630
expect(callbackHistory.length, equals(1));
631631
callbackResults = callbackHistory[callbackHistory.length - 1];
632632
expect(callbackResults['index'], equals(1));
633-
expect(callbackResults['isExpanded'], equals(false));
633+
expect(callbackResults['isExpanded'], equals(true));
634634

635635
// Close a different panel
636636
await tester.tap(find.byType(ExpandIcon).at(2));
@@ -639,13 +639,108 @@ void main() {
639639
// Callback is invoked the first time with correct arguments
640640
expect(callbackHistory.length, equals(3));
641641
callbackResults = callbackHistory[callbackHistory.length - 2];
642-
expect(callbackResults['index'], equals(2));
642+
expect(callbackResults['index'], equals(1));
643643
expect(callbackResults['isExpanded'], equals(false));
644644

645645
// Callback is invoked the second time with correct arguments
646646
callbackResults = callbackHistory[callbackHistory.length - 1];
647-
expect(callbackResults['index'], equals(1));
648-
expect(callbackResults['isExpanded'], equals(false));
647+
expect(callbackResults['index'], equals(2));
648+
expect(callbackResults['isExpanded'], equals(true));
649+
});
650+
651+
testWidgets('ExpansionPanelList.radio callback displays true or false based on the visibility of a list item', (WidgetTester tester) async {
652+
late int lastExpanded;
653+
bool topElementExpanded = false;
654+
bool bottomElementExpanded = false;
655+
656+
final List<ExpansionPanel> demoItemsRadio = <ExpansionPanelRadio>[
657+
// topElement
658+
ExpansionPanelRadio(
659+
headerBuilder: (BuildContext context, bool isExpanded) {
660+
return Text(isExpanded ? 'B' : 'A');
661+
},
662+
body: const SizedBox(height: 100.0),
663+
value: 0,
664+
),
665+
// bottomElement
666+
ExpansionPanelRadio(
667+
headerBuilder: (BuildContext context, bool isExpanded) {
668+
return Text(isExpanded ? 'D' : 'C');
669+
},
670+
body: const SizedBox(height: 100.0),
671+
value: 1,
672+
),
673+
];
674+
675+
final ExpansionPanelList expansionListRadio = ExpansionPanelList.radio(
676+
children: demoItemsRadio,
677+
expansionCallback: (int index, bool isExpanded)
678+
{
679+
lastExpanded = index;
680+
if (index == 0)
681+
{
682+
topElementExpanded = isExpanded;
683+
bottomElementExpanded = false;
684+
}
685+
else
686+
{
687+
topElementExpanded = false;
688+
bottomElementExpanded = isExpanded;
689+
}
690+
}
691+
);
692+
693+
await tester.pumpWidget(
694+
MaterialApp(
695+
home: SingleChildScrollView(
696+
child: expansionListRadio,
697+
),
698+
),
699+
);
700+
701+
// Initializes with all panels closed.
702+
expect(find.text('A'), findsOneWidget);
703+
expect(find.text('B'), findsNothing);
704+
expect(find.text('C'), findsOneWidget);
705+
expect(find.text('D'), findsNothing);
706+
707+
await tester.tap(find.byType(ExpandIcon).at(0));
708+
await tester.pump(const Duration(milliseconds: 200));
709+
await tester.pumpAndSettle();
710+
711+
// Now the first panel is open.
712+
expect(find.text('A'), findsNothing);
713+
expect(find.text('B'), findsOneWidget);
714+
expect(find.text('C'), findsOneWidget);
715+
expect(find.text('D'), findsNothing);
716+
717+
expect(lastExpanded,0);
718+
expect(topElementExpanded,true);
719+
720+
await tester.tap(find.byType(ExpandIcon).at(1));
721+
await tester.pump(const Duration(milliseconds: 200));
722+
await tester.pumpAndSettle();
723+
724+
// Open the other panel and ensure the first is now closed.
725+
expect(lastExpanded,1);
726+
expect(bottomElementExpanded,true);
727+
expect(topElementExpanded,false);
728+
expect(find.text('D'), findsOneWidget);
729+
expect(find.text('A'), findsOneWidget);
730+
731+
await tester.tap(find.byType(ExpandIcon).at(1));
732+
await tester.pump(const Duration(milliseconds: 200));
733+
await tester.pumpAndSettle();
734+
735+
// Close the item that was expanded should now be false.
736+
expect(lastExpanded,1);
737+
expect(bottomElementExpanded,false);
738+
739+
// All panels should be closed.
740+
expect(find.text('A'), findsOneWidget);
741+
expect(find.text('B'), findsNothing);
742+
expect(find.text('C'), findsOneWidget);
743+
expect(find.text('D'), findsNothing);
649744
});
650745

651746
testWidgets(

0 commit comments

Comments
 (0)