finalizeTree method
Complete the element build pass by unmounting any elements that are no longer active.
This is called by WidgetsBinding.drawFrame.
In debug mode, this also runs some sanity checks, for example checking for duplicate global keys.
After the current call stack unwinds, a microtask that notifies listeners about changes to global keys will run.
Implementation
void finalizeTree() {
Timeline.startSync('Finalize tree', arguments: timelineWhitelistArguments);
try {
lockState(() {
_inactiveElements._unmountAll(); // this unregisters the GlobalKeys
});
assert(() {
try {
GlobalKey._debugVerifyIllFatedPopulation();
if (_debugElementsThatWillNeedToBeRebuiltDueToGlobalKeyShenanigans != null &&
_debugElementsThatWillNeedToBeRebuiltDueToGlobalKeyShenanigans.isNotEmpty) {
final Set<GlobalKey> keys = HashSet<GlobalKey>();
for (Element element in _debugElementsThatWillNeedToBeRebuiltDueToGlobalKeyShenanigans.keys) {
if (element._debugLifecycleState != _ElementLifecycle.defunct)
keys.addAll(_debugElementsThatWillNeedToBeRebuiltDueToGlobalKeyShenanigans[element]);
}
if (keys.isNotEmpty) {
final Map<String, int> keyStringCount = HashMap<String, int>();
for (String key in keys.map<String>((GlobalKey key) => key.toString())) {
if (keyStringCount.containsKey(key)) {
keyStringCount[key] += 1;
} else {
keyStringCount[key] = 1;
}
}
final List<String> keyLabels = <String>[];
keyStringCount.forEach((String key, int count) {
if (count == 1) {
keyLabels.add(key);
} else {
keyLabels.add('$key ($count different affected keys had this toString representation)');
}
});
final Iterable<Element> elements = _debugElementsThatWillNeedToBeRebuiltDueToGlobalKeyShenanigans.keys;
final Map<String, int> elementStringCount = HashMap<String, int>();
for (String element in elements.map<String>((Element element) => element.toString())) {
if (elementStringCount.containsKey(element)) {
elementStringCount[element] += 1;
} else {
elementStringCount[element] = 1;
}
}
final List<String> elementLabels = <String>[];
elementStringCount.forEach((String element, int count) {
if (count == 1) {
elementLabels.add(element);
} else {
elementLabels.add('$element ($count different affected elements had this toString representation)');
}
});
assert(keyLabels.isNotEmpty);
final String the = keys.length == 1 ? ' the' : '';
final String s = keys.length == 1 ? '' : 's';
final String were = keys.length == 1 ? 'was' : 'were';
final String their = keys.length == 1 ? 'its' : 'their';
final String respective = elementLabels.length == 1 ? '' : ' respective';
final String those = keys.length == 1 ? 'that' : 'those';
final String s2 = elementLabels.length == 1 ? '' : 's';
final String those2 = elementLabels.length == 1 ? 'that' : 'those';
final String they = elementLabels.length == 1 ? 'it' : 'they';
final String think = elementLabels.length == 1 ? 'thinks' : 'think';
final String are = elementLabels.length == 1 ? 'is' : 'are';
throw FlutterError(
'Duplicate GlobalKey$s detected in widget tree.\n'
'The following GlobalKey$s $were specified multiple times in the widget tree. This will lead to '
'parts of the widget tree being truncated unexpectedly, because the second time a key is seen, '
'the previous instance is moved to the new location. The key$s $were:\n'
'- ${keyLabels.join("\n ")}\n'
'This was determined by noticing that after$the widget$s with the above global key$s $were moved '
'out of $their$respective previous parent$s2, $those2 previous parent$s2 never updated during this frame, meaning '
'that $they either did not update at all or updated before the widget$s $were moved, in either case '
'implying that $they still $think that $they should have a child with $those global key$s.\n'
'The specific parent$s2 that did not update after having one or more children forcibly removed '
'due to GlobalKey reparenting $are:\n'
'- ${elementLabels.join("\n ")}\n'
'A GlobalKey can only be specified on one widget at a time in the widget tree.'
);
}
}
} finally {
_debugElementsThatWillNeedToBeRebuiltDueToGlobalKeyShenanigans?.clear();
}
return true;
}());
} catch (e, stack) {
_debugReportException('while finalizing the widget tree', e, stack);
} finally {
Timeline.finishSync();
}
}