Unverified Commit 39712854 authored by Shi-Hao Hong's avatar Shi-Hao Hong Committed by GitHub

Fix ExpansionPanelList Duplicate Global Keys Exception (#31228)

- Move `setState` to only be invoked when guarded by `widget._allowMultiplePanelsOpen`, fixing the case for `ExpansionPanelList`

- Remove setting `_currentOpenPanel` to `widget.initialOpenPanelValue` in `didUpdateWidget`, since this should only occur on `initState` and not every time the widget is updated. This fixes the problem for `ExpansionPanelList.radio`

- Added a `didUpdateWidget` condition for when `ExpansionPanelList` changes into `ExpansionPanelList.radio` to open the panel at `widget.initialOpenPanelValue`

- Added test cases for regression, expansionCallback cases, and `didUpdateWidget` transitions between `ExpansionPanelList` and `ExpansionPanelList.radio`
parent 37e25238
......@@ -133,6 +133,9 @@ class ExpansionPanelRadio extends ExpansionPanel {
/// A material expansion panel list that lays out its children and animates
/// expansions.
///
/// Note that [expansionCallback] behaves differently for [ExpansionPanelList]
/// and [ExpansionPanelList.radio].
///
/// {@tool snippet --template=stateful_widget_scaffold}
///
/// Here is a simple example of how to implement ExpansionPanelList.
......@@ -319,8 +322,17 @@ class ExpansionPanelList extends StatefulWidget {
/// The callback that gets called whenever one of the expand/collapse buttons
/// is pressed. The arguments passed to the callback are the index of the
/// to-be-expanded panel in the list and whether the panel is currently
/// expanded or not.
/// pressed panel and whether the panel is currently expanded or not.
///
/// If ExpansionPanelList.radio is used, the callback may be called a
/// second time if a different panel was previously open. The arguments
/// passed to the second callback are the index of the panel that will close
/// and false, marking that it will be closed.
///
/// For ExpansionPanelList, the callback needs to setState when it's notified
/// about the closing/opening panel. On the other hand, the callback for
/// ExpansionPanelList.radio is simply meant to inform the parent widget of
/// changes, as the radio panels' open/close states are managed internally.
///
/// This callback is useful in order to keep track of the expanded/collapsed
/// panels in a parent widget that may need to react to these changes.
......@@ -348,11 +360,9 @@ class _ExpansionPanelListState extends State<ExpansionPanelList> {
void initState() {
super.initState();
if (widget._allowOnlyOnePanelOpen) {
assert(_allIdentifiersUnique(), 'All object identifiers are not unique!');
for (ExpansionPanelRadio child in widget.children) {
if (widget.initialOpenPanelValue != null &&
child.value == widget.initialOpenPanelValue)
_currentOpenPanel = child;
assert(_allIdentifiersUnique(), 'All ExpansionPanelRadio identifier values must be unique.');
if (widget.initialOpenPanelValue != null) {
_currentOpenPanel = searchPanelByValue(widget.children, widget.initialOpenPanelValue);
}
}
}
......@@ -360,14 +370,15 @@ class _ExpansionPanelListState extends State<ExpansionPanelList> {
@override
void didUpdateWidget(ExpansionPanelList oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget._allowOnlyOnePanelOpen) {
assert(_allIdentifiersUnique(), 'All object identifiers are not unique!');
for (ExpansionPanelRadio newChild in widget.children) {
if (widget.initialOpenPanelValue != null &&
newChild.value == widget.initialOpenPanelValue)
_currentOpenPanel = newChild;
assert(_allIdentifiersUnique(), 'All ExpansionPanelRadio identifier values must be unique.');
// If the previous widget was non-radio ExpansionPanelList, initialize the
// open panel to widget.initialOpenPanelValue
if (!oldWidget._allowOnlyOnePanelOpen) {
_currentOpenPanel = searchPanelByValue(widget.children, widget.initialOpenPanelValue);
}
} else if (oldWidget._allowOnlyOnePanelOpen) {
} else {
_currentOpenPanel = null;
}
}
......@@ -395,6 +406,8 @@ class _ExpansionPanelListState extends State<ExpansionPanelList> {
if (widget._allowOnlyOnePanelOpen) {
final ExpansionPanelRadio pressedChild = widget.children[index];
// If another ExpansionPanelRadio was already open, apply its
// expansionCallback (if any) to false, because it's closing.
for (int childIndex = 0; childIndex < widget.children.length; childIndex += 1) {
final ExpansionPanelRadio child = widget.children[childIndex];
if (widget.expansionCallback != null &&
......@@ -402,9 +415,19 @@ class _ExpansionPanelListState extends State<ExpansionPanelList> {
child.value == _currentOpenPanel?.value)
widget.expansionCallback(childIndex, false);
}
_currentOpenPanel = isExpanded ? null : pressedChild;
setState(() {
_currentOpenPanel = isExpanded ? null : pressedChild;
});
}
}
ExpansionPanelRadio searchPanelByValue(List<ExpansionPanelRadio> panels, Object value) {
for (ExpansionPanelRadio panel in panels) {
if (panel.value == value)
return panel;
}
setState(() { });
return null;
}
@override
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment