@@ -2053,13 +2053,54 @@ abstract class MultiChildRenderObjectWidget extends RenderObjectWidget {
2053
2053
2054
2054
// ELEMENTS
2055
2055
2056
- enum _ElementLifecycle { initial, active, inactive, defunct }
2056
+ enum _ElementLifecycle {
2057
+ /// The [Element] is created but has not yet been incorporated into the element
2058
+ /// tree.
2059
+ initial,
2060
+
2061
+ /// The [Element] is incorporated into the Element tree, either via
2062
+ /// [Element.mount] or [Element.activate] .
2063
+ active,
2064
+
2065
+ /// The previously `active` [Element] is removed from the Element tree via
2066
+ /// [Element.deactivate] .
2067
+ ///
2068
+ /// This [Element] may become `active` again if a parent reclaims it using
2069
+ /// a [GlobalKey] , or `defunct` if no parent reclaims it at the end of the
2070
+ /// build phase.
2071
+ inactive,
2072
+
2073
+ /// The [Element] encountered an unrecoverable error while being rebuilt when it
2074
+ /// was `active` or while being incorporated in the tree.
2075
+ ///
2076
+ /// This indicates the [Element] 's subtree is in an inconsistent state and must
2077
+ /// not be re-incorporated into the tree again.
2078
+ ///
2079
+ /// When an unrecoverable error is encountered, the framework calls
2080
+ /// [Element.deactivate] on this [Element] and sets its state to `failed` . This
2081
+ /// process is done on a best-effort basis and does not surface any additional
2082
+ /// errors.
2083
+ ///
2084
+ /// This is one of the two final stages of the element lifecycle and is not
2085
+ /// reversible. Reaching this state typically means that a widget implementation
2086
+ /// is throwing unhandled exceptions that need to be properly handled.
2087
+ failed,
2088
+
2089
+ /// The [Element] is disposed and should not be interacted with.
2090
+ ///
2091
+ /// The [Element] must be `inactive` before transitioning into this state,
2092
+ /// and the state transition occurs in [BuildOwner.finalizeTree] which signals
2093
+ /// the end of the build phase.
2094
+ ///
2095
+ /// This is the final stage of the element lifecycle and is not reversible.
2096
+ defunct,
2097
+ }
2057
2098
2058
2099
class _InactiveElements {
2059
2100
bool _locked = false ;
2060
2101
final Set <Element > _elements = HashSet <Element >();
2061
2102
2062
- void _unmount (Element element) {
2103
+ static void _unmount (Element element) {
2063
2104
assert (element._lifecycleState == _ElementLifecycle .inactive);
2064
2105
assert (() {
2065
2106
if (debugPrintGlobalKeyedWidgetLifecycle) {
@@ -2091,8 +2132,12 @@ class _InactiveElements {
2091
2132
2092
2133
static void _deactivateRecursively (Element element) {
2093
2134
assert (element._lifecycleState == _ElementLifecycle .active);
2094
- element.deactivate ();
2095
- assert (element._lifecycleState == _ElementLifecycle .inactive);
2135
+ try {
2136
+ element.deactivate ();
2137
+ } catch (_) {
2138
+ Element ._deactivateFailedSubtreeRecursively (element);
2139
+ rethrow ;
2140
+ }
2096
2141
element.visitChildren (_deactivateRecursively);
2097
2142
assert (() {
2098
2143
element.debugDeactivated ();
@@ -2104,18 +2149,26 @@ class _InactiveElements {
2104
2149
assert (! _locked);
2105
2150
assert (! _elements.contains (element));
2106
2151
assert (element._parent == null );
2107
- if (element._lifecycleState == _ElementLifecycle .active) {
2108
- _deactivateRecursively (element);
2152
+
2153
+ switch (element._lifecycleState) {
2154
+ case _ElementLifecycle .active:
2155
+ _deactivateRecursively (element);
2156
+ // This element is only added to _elements if the whole subtree is
2157
+ // successfully deactivated.
2158
+ _elements.add (element);
2159
+ case _ElementLifecycle .inactive:
2160
+ _elements.add (element);
2161
+ case _ElementLifecycle .initial || _ElementLifecycle .failed || _ElementLifecycle .defunct:
2162
+ assert (false , '$element must not be deactivated when in ${element ._lifecycleState } state.' );
2109
2163
}
2110
- _elements.add (element);
2111
2164
}
2112
2165
2113
2166
void remove (Element element) {
2114
2167
assert (! _locked);
2115
2168
assert (_elements.contains (element));
2116
2169
assert (element._parent == null );
2117
2170
_elements.remove (element);
2118
- assert (element._lifecycleState != _ElementLifecycle .active );
2171
+ assert (element._lifecycleState == _ElementLifecycle .inactive );
2119
2172
}
2120
2173
2121
2174
bool debugContains (Element element) {
@@ -2934,7 +2987,7 @@ class BuildOwner {
2934
2987
buildScope._scheduleBuildFor (element);
2935
2988
assert (() {
2936
2989
if (debugPrintScheduleBuildForStacks) {
2937
- debugPrint ("...the build scope's dirty list is now: $buildScope ._dirtyElements" );
2990
+ debugPrint ("...the build scope's dirty list is now: ${ buildScope ._dirtyElements } " );
2938
2991
}
2939
2992
return true ;
2940
2993
}());
@@ -4516,39 +4569,32 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
4516
4569
4517
4570
try {
4518
4571
final Key ? key = newWidget.key;
4519
- if (key is GlobalKey ) {
4520
- final Element ? newChild = _retakeInactiveElement (key, newWidget);
4521
- if (newChild != null ) {
4522
- assert (newChild._parent == null );
4523
- assert (() {
4524
- _debugCheckForCycles (newChild);
4525
- return true ;
4526
- }());
4527
- try {
4528
- newChild._activateWithParent (this , newSlot);
4529
- } catch (_) {
4530
- // Attempt to do some clean-up if activation fails to leave tree in a reasonable state.
4531
- try {
4532
- deactivateChild (newChild);
4533
- } catch (_) {
4534
- // Clean-up failed. Only surface original exception.
4535
- }
4536
- rethrow ;
4537
- }
4538
- final Element ? updatedChild = updateChild (newChild, newWidget, newSlot);
4539
- assert (newChild == updatedChild);
4540
- return updatedChild! ;
4541
- }
4542
- }
4543
- final Element newChild = newWidget.createElement ();
4572
+ final Element ? inactiveChild = key is GlobalKey
4573
+ ? _retakeInactiveElement (key, newWidget)
4574
+ : null ;
4575
+ final Element newChild = inactiveChild ?? newWidget.createElement ();
4544
4576
assert (() {
4545
4577
_debugCheckForCycles (newChild);
4546
4578
return true ;
4547
4579
}());
4548
- newChild.mount (this , newSlot);
4549
- assert (newChild._lifecycleState == _ElementLifecycle .active);
4550
-
4551
- return newChild;
4580
+ try {
4581
+ if (inactiveChild != null ) {
4582
+ assert (inactiveChild._parent == null );
4583
+ inactiveChild._activateWithParent (this , newSlot);
4584
+ final Element ? updatedChild = updateChild (inactiveChild, newWidget, newSlot);
4585
+ assert (inactiveChild == updatedChild);
4586
+ return updatedChild! ;
4587
+ } else {
4588
+ newChild.mount (this , newSlot);
4589
+ assert (newChild._lifecycleState == _ElementLifecycle .active);
4590
+ return newChild;
4591
+ }
4592
+ } catch (_) {
4593
+ // Attempt to do some clean-up if activation or mount fails
4594
+ // to leave tree in a reasonable state.
4595
+ _deactivateFailedChildSilently (newChild);
4596
+ rethrow ;
4597
+ }
4552
4598
} finally {
4553
4599
if (isTimelineTracked) {
4554
4600
FlutterTimeline .finishSync ();
@@ -4583,6 +4629,7 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
4583
4629
/// parent proactively calls the old parent's [deactivateChild] , first using
4584
4630
/// [forgetChild] to cause the old parent to update its child model.
4585
4631
@protected
4632
+ @mustCallSuper
4586
4633
void deactivateChild (Element child) {
4587
4634
assert (child._parent == this );
4588
4635
child._parent = null ;
@@ -4598,6 +4645,38 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
4598
4645
}());
4599
4646
}
4600
4647
4648
+ void _deactivateFailedChildSilently (Element child) {
4649
+ try {
4650
+ child._parent = null ;
4651
+ child.detachRenderObject ();
4652
+ _deactivateFailedSubtreeRecursively (child);
4653
+ } catch (_) {
4654
+ // Do not rethrow:
4655
+ // The subtree has already thrown a different error and the framework is
4656
+ // cleaning up on a best-effort basis.
4657
+ }
4658
+ }
4659
+
4660
+ // This method calls _ensureDeactivated for the subtree rooted at `element`,
4661
+ // supressing all exceptions thrown.
4662
+ //
4663
+ // This method will attempt to keep doing treewalk even one of the nodes
4664
+ // failed to deactivate.
4665
+ //
4666
+ // The subtree has already thrown a different error and the framework is
4667
+ // cleaning up on a best-effort basis.
4668
+ static void _deactivateFailedSubtreeRecursively (Element element) {
4669
+ try {
4670
+ element.deactivate ();
4671
+ } catch (_) {
4672
+ element._ensureDeactivated ();
4673
+ }
4674
+ element._lifecycleState = _ElementLifecycle .failed;
4675
+ try {
4676
+ element.visitChildren (_deactivateFailedSubtreeRecursively);
4677
+ } catch (_) {}
4678
+ }
4679
+
4601
4680
// The children that have been forgotten by forgetChild. This will be used in
4602
4681
// [update] to remove the global key reservations of forgotten children.
4603
4682
//
@@ -4672,6 +4751,7 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
4672
4751
/// Implementations of this method should start with a call to the inherited
4673
4752
/// method, as in `super.activate()` .
4674
4753
@mustCallSuper
4754
+ @visibleForOverriding
4675
4755
void activate () {
4676
4756
assert (_lifecycleState == _ElementLifecycle .inactive);
4677
4757
assert (owner != null );
@@ -4701,18 +4781,34 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
4701
4781
/// animation frame, if the element has not be reactivated, the framework will
4702
4782
/// unmount the element.
4703
4783
///
4704
- /// This is (indirectly) called by [deactivateChild] .
4784
+ /// In case of an uncaught exception when rebuild a widget subtree, the
4785
+ /// framework also calls this method on the failing subtree to make sure the
4786
+ /// widget tree is in a relatively consistent state. The deactivation of such
4787
+ /// subtrees are performed only on a best-effort basis, and the errors thrown
4788
+ /// during deactivation will not be rethrown.
4789
+ ///
4790
+ /// This is indirectly called by [deactivateChild] .
4705
4791
///
4706
4792
/// See the lifecycle documentation for [Element] for additional information.
4707
4793
///
4708
4794
/// Implementations of this method should end with a call to the inherited
4709
4795
/// method, as in `super.deactivate()` .
4710
4796
@mustCallSuper
4797
+ @visibleForOverriding
4711
4798
void deactivate () {
4712
4799
assert (_lifecycleState == _ElementLifecycle .active);
4713
4800
assert (_widget != null ); // Use the private property to avoid a CastError during hot reload.
4714
- if (_dependencies? .isNotEmpty ?? false ) {
4715
- for (final InheritedElement dependency in _dependencies! ) {
4801
+ _ensureDeactivated ();
4802
+ }
4803
+
4804
+ /// Removes dependencies and sets the lifecycle state of this [Element] to
4805
+ /// inactive.
4806
+ ///
4807
+ /// This method is immediately called after [Element.deactivate] , even if that
4808
+ /// call throws an exception.
4809
+ void _ensureDeactivated () {
4810
+ if (_dependencies case final Set <InheritedElement > dependencies? when dependencies.isNotEmpty) {
4811
+ for (final InheritedElement dependency in dependencies) {
4716
4812
dependency.removeDependent (this );
4717
4813
}
4718
4814
// For expediency, we don't actually clear the list here, even though it's
@@ -4977,8 +5073,7 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
4977
5073
4978
5074
@override
4979
5075
InheritedWidget dependOnInheritedElement (InheritedElement ancestor, {Object ? aspect}) {
4980
- _dependencies ?? = HashSet <InheritedElement >();
4981
- _dependencies! .add (ancestor);
5076
+ (_dependencies ?? = HashSet <InheritedElement >()).add (ancestor);
4982
5077
ancestor.updateDependencies (this , aspect);
4983
5078
return ancestor.widget as InheritedWidget ;
4984
5079
}
@@ -5714,7 +5809,7 @@ abstract class ComponentElement extends Element {
5714
5809
@override
5715
5810
@pragma ('vm:notify-debugger-on-exception' )
5716
5811
void performRebuild () {
5717
- Widget ? built;
5812
+ Widget built;
5718
5813
try {
5719
5814
assert (() {
5720
5815
_debugDoingBuild = true ;
@@ -5757,6 +5852,10 @@ abstract class ComponentElement extends Element {
5757
5852
],
5758
5853
),
5759
5854
);
5855
+ // _Make sure the old child subtree are deactivated and disposed.
5856
+ try {
5857
+ _child? .deactivate ();
5858
+ } catch (_) {}
5760
5859
_child = updateChild (null , built, slot);
5761
5860
}
5762
5861
}
@@ -6167,10 +6266,7 @@ class InheritedElement extends ProxyElement {
6167
6266
6168
6267
@override
6169
6268
void debugDeactivated () {
6170
- assert (() {
6171
- assert (_dependents.isEmpty);
6172
- return true ;
6173
- }());
6269
+ assert (_dependents.isEmpty);
6174
6270
super .debugDeactivated ();
6175
6271
}
6176
6272
@@ -6748,6 +6844,7 @@ abstract class RenderObjectElement extends Element {
6748
6844
}
6749
6845
6750
6846
@override
6847
+ @visibleForOverriding
6751
6848
void deactivate () {
6752
6849
super .deactivate ();
6753
6850
assert (
@@ -6758,6 +6855,7 @@ abstract class RenderObjectElement extends Element {
6758
6855
}
6759
6856
6760
6857
@override
6858
+ @visibleForOverriding
6761
6859
void unmount () {
6762
6860
assert (
6763
6861
! renderObject.debugDisposed! ,
0 commit comments