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 { ...@@ -133,6 +133,9 @@ class ExpansionPanelRadio extends ExpansionPanel {
/// A material expansion panel list that lays out its children and animates /// A material expansion panel list that lays out its children and animates
/// expansions. /// expansions.
/// ///
/// Note that [expansionCallback] behaves differently for [ExpansionPanelList]
/// and [ExpansionPanelList.radio].
///
/// {@tool snippet --template=stateful_widget_scaffold} /// {@tool snippet --template=stateful_widget_scaffold}
/// ///
/// Here is a simple example of how to implement ExpansionPanelList. /// Here is a simple example of how to implement ExpansionPanelList.
...@@ -319,8 +322,17 @@ class ExpansionPanelList extends StatefulWidget { ...@@ -319,8 +322,17 @@ class ExpansionPanelList extends StatefulWidget {
/// The callback that gets called whenever one of the expand/collapse buttons /// 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 /// 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 /// pressed panel and whether the panel is currently expanded or not.
/// 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 /// 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. /// panels in a parent widget that may need to react to these changes.
...@@ -348,11 +360,9 @@ class _ExpansionPanelListState extends State<ExpansionPanelList> { ...@@ -348,11 +360,9 @@ class _ExpansionPanelListState extends State<ExpansionPanelList> {
void initState() { void initState() {
super.initState(); super.initState();
if (widget._allowOnlyOnePanelOpen) { if (widget._allowOnlyOnePanelOpen) {
assert(_allIdentifiersUnique(), 'All object identifiers are not unique!'); assert(_allIdentifiersUnique(), 'All ExpansionPanelRadio identifier values must be unique.');
for (ExpansionPanelRadio child in widget.children) { if (widget.initialOpenPanelValue != null) {
if (widget.initialOpenPanelValue != null && _currentOpenPanel = searchPanelByValue(widget.children, widget.initialOpenPanelValue);
child.value == widget.initialOpenPanelValue)
_currentOpenPanel = child;
} }
} }
} }
...@@ -360,14 +370,15 @@ class _ExpansionPanelListState extends State<ExpansionPanelList> { ...@@ -360,14 +370,15 @@ class _ExpansionPanelListState extends State<ExpansionPanelList> {
@override @override
void didUpdateWidget(ExpansionPanelList oldWidget) { void didUpdateWidget(ExpansionPanelList oldWidget) {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
if (widget._allowOnlyOnePanelOpen) { if (widget._allowOnlyOnePanelOpen) {
assert(_allIdentifiersUnique(), 'All object identifiers are not unique!'); assert(_allIdentifiersUnique(), 'All ExpansionPanelRadio identifier values must be unique.');
for (ExpansionPanelRadio newChild in widget.children) { // If the previous widget was non-radio ExpansionPanelList, initialize the
if (widget.initialOpenPanelValue != null && // open panel to widget.initialOpenPanelValue
newChild.value == widget.initialOpenPanelValue) if (!oldWidget._allowOnlyOnePanelOpen) {
_currentOpenPanel = newChild; _currentOpenPanel = searchPanelByValue(widget.children, widget.initialOpenPanelValue);
} }
} else if (oldWidget._allowOnlyOnePanelOpen) { } else {
_currentOpenPanel = null; _currentOpenPanel = null;
} }
} }
...@@ -395,6 +406,8 @@ class _ExpansionPanelListState extends State<ExpansionPanelList> { ...@@ -395,6 +406,8 @@ class _ExpansionPanelListState extends State<ExpansionPanelList> {
if (widget._allowOnlyOnePanelOpen) { if (widget._allowOnlyOnePanelOpen) {
final ExpansionPanelRadio pressedChild = widget.children[index]; 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) { for (int childIndex = 0; childIndex < widget.children.length; childIndex += 1) {
final ExpansionPanelRadio child = widget.children[childIndex]; final ExpansionPanelRadio child = widget.children[childIndex];
if (widget.expansionCallback != null && if (widget.expansionCallback != null &&
...@@ -402,9 +415,19 @@ class _ExpansionPanelListState extends State<ExpansionPanelList> { ...@@ -402,9 +415,19 @@ class _ExpansionPanelListState extends State<ExpansionPanelList> {
child.value == _currentOpenPanel?.value) child.value == _currentOpenPanel?.value)
widget.expansionCallback(childIndex, false); 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 @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