Unverified Commit e7266dbb authored by YeungKC's avatar YeungKC Committed by GitHub

Let InkWell/Ink/ancestor support GlobalKey so that splash does not stop when...

Let InkWell/Ink/ancestor support GlobalKey so that splash does not stop when changing position. (#71138)
parent e384ca79
...@@ -251,9 +251,21 @@ class _InkState extends State<Ink> { ...@@ -251,9 +251,21 @@ class _InkState extends State<Ink> {
@override @override
void deactivate() { void deactivate() {
_ink?.visible = false;
super.deactivate();
}
@override
void reactivate() {
_ink?.visible = true;
super.reactivate();
}
@override
void dispose() {
_ink?.dispose(); _ink?.dispose();
assert(_ink == null); assert(_ink == null);
super.deactivate(); super.dispose();
} }
Widget _build(BuildContext context) { Widget _build(BuildContext context) {
......
...@@ -733,6 +733,7 @@ class _InkResponseState extends State<_InkResponseStateWidget> ...@@ -733,6 +733,7 @@ class _InkResponseState extends State<_InkResponseStateWidget>
implements _ParentInkResponseState { implements _ParentInkResponseState {
Set<InteractiveInkFeature>? _splashes; Set<InteractiveInkFeature>? _splashes;
InteractiveInkFeature? _currentSplash; InteractiveInkFeature? _currentSplash;
bool _active = true;
bool _hovering = false; bool _hovering = false;
final Map<_HighlightType, InkHighlight?> _highlights = <_HighlightType, InkHighlight?>{}; final Map<_HighlightType, InkHighlight?> _highlights = <_HighlightType, InkHighlight?>{};
late final Map<Type, Action<Intent>> _actionMap = <Type, Action<Intent>>{ late final Map<Type, Action<Intent>> _actionMap = <Type, Action<Intent>>{
...@@ -779,7 +780,16 @@ class _InkResponseState extends State<_InkResponseStateWidget> ...@@ -779,7 +780,16 @@ class _InkResponseState extends State<_InkResponseStateWidget>
@override @override
void didUpdateWidget(_InkResponseStateWidget oldWidget) { void didUpdateWidget(_InkResponseStateWidget oldWidget) {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
if (_isWidgetEnabled(widget) != _isWidgetEnabled(oldWidget)) { final InkFeature? validInkFeature = _getSingleInkFeature();
if (validInkFeature != null && !identical(validInkFeature.controller, Material.of(context)!)) {
_removeAllFeatures();
if (_hovering && enabled)
updateHighlight(_HighlightType.hover, value: _hovering, callOnHover: false);
_updateFocusHighlights();
return;
}
if (enabled != _isWidgetEnabled(oldWidget)) {
if (enabled) { if (enabled) {
// Don't call wigdet.onHover because many wigets, including the button // Don't call wigdet.onHover because many wigets, including the button
// widgets, apply setState to an ancestor context from onHover. // widgets, apply setState to an ancestor context from onHover.
...@@ -789,12 +799,47 @@ class _InkResponseState extends State<_InkResponseStateWidget> ...@@ -789,12 +799,47 @@ class _InkResponseState extends State<_InkResponseStateWidget>
} }
} }
InkFeature? _getSingleInkFeature() {
final List<InkFeature?> inkFeatures = <InkFeature?>[...?_splashes, ..._highlights.values];
assert(() {
MaterialInkController? lastController;
for (final InkFeature? inkFeature in inkFeatures) {
if (inkFeature == null)
continue;
final MaterialInkController controller = inkFeature.controller;
if (lastController != null && !identical(controller, lastController))
return false;
lastController = controller;
}
return true;
}());
final InkFeature? validInkFeature = inkFeatures.firstWhere((InkFeature? inkFeature) => inkFeature != null, orElse: () => null);
return validInkFeature;
}
@override @override
void dispose() { void dispose() {
_removeAllFeatures();
FocusManager.instance.removeHighlightModeListener(_handleFocusHighlightModeChange); FocusManager.instance.removeHighlightModeListener(_handleFocusHighlightModeChange);
super.dispose(); super.dispose();
} }
void _removeAllFeatures() {
if (_splashes != null) {
final Set<InteractiveInkFeature> splashes = _splashes!;
_splashes = null;
for (final InteractiveInkFeature splash in splashes)
splash.dispose();
_currentSplash = null;
}
assert(_currentSplash == null);
for (final _HighlightType highlight in _highlights.keys) {
_highlights[highlight]?.dispose();
_highlights[highlight] = null;
}
widget.parentState?.markChildInkResponsePressed(this, false);
}
@override @override
bool get wantKeepAlive => highlightsExist || (_splashes != null && _splashes!.isNotEmpty); bool get wantKeepAlive => highlightsExist || (_splashes != null && _splashes!.isNotEmpty);
...@@ -830,7 +875,8 @@ class _InkResponseState extends State<_InkResponseStateWidget> ...@@ -830,7 +875,8 @@ class _InkResponseState extends State<_InkResponseStateWidget>
void handleInkRemoval() { void handleInkRemoval() {
assert(_highlights[type] != null); assert(_highlights[type] != null);
_highlights[type] = null; _highlights[type] = null;
updateKeepAlive(); if (_active)
updateKeepAlive();
} }
if (type == _HighlightType.pressed) { if (type == _HighlightType.pressed) {
...@@ -1013,24 +1059,26 @@ class _InkResponseState extends State<_InkResponseStateWidget> ...@@ -1013,24 +1059,26 @@ class _InkResponseState extends State<_InkResponseStateWidget>
} }
} }
void _setAllFeaturesVisible(bool visible) {
for (final InkFeature? splash in <InkFeature?>[...?_splashes, ..._highlights.values])
splash?.visible = visible;
}
@override @override
void deactivate() { void deactivate() {
if (_splashes != null) { _active = !_active;
final Set<InteractiveInkFeature> splashes = _splashes!; _setAllFeaturesVisible(false);
_splashes = null;
for (final InteractiveInkFeature splash in splashes)
splash.dispose();
_currentSplash = null;
}
assert(_currentSplash == null);
for (final _HighlightType highlight in _highlights.keys) {
_highlights[highlight]?.dispose();
_highlights[highlight] = null;
}
widget.parentState?.markChildInkResponsePressed(this, false);
super.deactivate(); super.deactivate();
} }
@override
void reactivate() {
_active = !_active;
_setAllFeaturesVisible(true);
updateKeepAlive();
super.reactivate();
}
bool _isWidgetEnabled(_InkResponseStateWidget widget) { bool _isWidgetEnabled(_InkResponseStateWidget widget) {
return widget.onTap != null || widget.onDoubleTap != null || widget.onLongPress != null; return widget.onTap != null || widget.onDoubleTap != null || widget.onLongPress != null;
} }
...@@ -1201,6 +1249,10 @@ class _InkResponseState extends State<_InkResponseStateWidget> ...@@ -1201,6 +1249,10 @@ class _InkResponseState extends State<_InkResponseStateWidget>
/// during animation. You should avoid using InkWells within [Material] widgets /// during animation. You should avoid using InkWells within [Material] widgets
/// that are changing size. /// that are changing size.
/// ///
/// Animations triggered by an [InkWell] will survive their widget moving due
/// to [GlobalKey] reparenting, as long as the nearest [Material] ancestor is
/// the same before and after the move.
///
/// See also: /// See also:
/// ///
/// * [GestureDetector], for listening for gestures without ink splashes. /// * [GestureDetector], for listening for gestures without ink splashes.
......
...@@ -544,12 +544,20 @@ class _RenderInkFeatures extends RenderProxyBox implements MaterialInkController ...@@ -544,12 +544,20 @@ class _RenderInkFeatures extends RenderProxyBox implements MaterialInkController
canvas.save(); canvas.save();
canvas.translate(offset.dx, offset.dy); canvas.translate(offset.dx, offset.dy);
canvas.clipRect(Offset.zero & size); canvas.clipRect(Offset.zero & size);
for (final InkFeature inkFeature in _inkFeatures!) for (final InkFeature inkFeature in _inkFeatures!) {
inkFeature._paint(canvas); if (inkFeature.visible)
inkFeature._paint(canvas);
}
canvas.restore(); canvas.restore();
} }
super.paint(context, offset); super.paint(context, offset);
} }
void dispose() {
// [InkFeature.dispose] will eventually call [_inkFeatures!.remove].
while (_inkFeatures?.isNotEmpty == true)
_inkFeatures!.first.dispose();
}
} }
class _InkFeatures extends SingleChildRenderObjectWidget { class _InkFeatures extends SingleChildRenderObjectWidget {
...@@ -585,6 +593,11 @@ class _InkFeatures extends SingleChildRenderObjectWidget { ...@@ -585,6 +593,11 @@ class _InkFeatures extends SingleChildRenderObjectWidget {
..absorbHitTest = absorbHitTest; ..absorbHitTest = absorbHitTest;
assert(vsync == renderObject.vsync); assert(vsync == renderObject.vsync);
} }
@override
void didUnmountRenderObject(_RenderInkFeatures renderObject) {
renderObject.dispose();
}
} }
/// A visual reaction on a piece of [Material]. /// A visual reaction on a piece of [Material].
...@@ -617,6 +630,15 @@ abstract class InkFeature { ...@@ -617,6 +630,15 @@ abstract class InkFeature {
bool _debugDisposed = false; bool _debugDisposed = false;
/// Whether or not visual reaction is activated.
///
/// Change this field will affect whether this InkFeature is render in next
/// frame.
///
/// For this InkFeature to render properly, it should usually be change in
/// [State.deactivate] and [State.reactivate].
bool visible = true;
/// Free up the resources associated with this ink feature. /// Free up the resources associated with this ink feature.
@mustCallSuper @mustCallSuper
void dispose() { void dispose() {
......
...@@ -584,7 +584,6 @@ class _MergeableMaterialState extends State<MergeableMaterial> with TickerProvid ...@@ -584,7 +584,6 @@ class _MergeableMaterialState extends State<MergeableMaterial> with TickerProvid
} }
child = AnimatedContainer( child = AnimatedContainer(
key: _MergeableMaterialSliceKey(_children[i].key),
decoration: BoxDecoration(border: border), decoration: BoxDecoration(border: border),
duration: kThemeAnimationDuration, duration: kThemeAnimationDuration,
curve: Curves.fastOutSlowIn, curve: Curves.fastOutSlowIn,
...@@ -600,6 +599,7 @@ class _MergeableMaterialState extends State<MergeableMaterial> with TickerProvid ...@@ -600,6 +599,7 @@ class _MergeableMaterialState extends State<MergeableMaterial> with TickerProvid
shape: BoxShape.rectangle, shape: BoxShape.rectangle,
), ),
child: Material( child: Material(
key: _MergeableMaterialSliceKey(_children[i].key),
type: MaterialType.transparency, type: MaterialType.transparency,
child: child, child: child,
), ),
......
...@@ -930,6 +930,12 @@ abstract class State<T extends StatefulWidget> with Diagnosticable { ...@@ -930,6 +930,12 @@ abstract class State<T extends StatefulWidget> with Diagnosticable {
/// It is an error to call [setState] unless [mounted] is true. /// It is an error to call [setState] unless [mounted] is true.
bool get mounted => _element != null; bool get mounted => _element != null;
/// This field is used tracks [reactivate] and [deactivate], to assert that
/// they are called alternatively.
///
/// This field is not set in release mode.
bool _debugActive = true;
/// Called when this object is inserted into the tree. /// Called when this object is inserted into the tree.
/// ///
/// The framework will call this method exactly once for each [State] object /// The framework will call this method exactly once for each [State] object
...@@ -1110,17 +1116,17 @@ abstract class State<T extends StatefulWidget> with Diagnosticable { ...@@ -1110,17 +1116,17 @@ abstract class State<T extends StatefulWidget> with Diagnosticable {
_element!.markNeedsBuild(); _element!.markNeedsBuild();
} }
/// Called when this object is removed from the tree. /// Whenever the framework removes this [State] object from the tree, the
/// framework will call this method.
/// ///
/// The framework calls this method whenever it removes this [State] object /// In some cases, the framework will reinsert the [State] object into
/// from the tree. In some cases, the framework will reinsert the [State] /// another part of the tree (e.g., if the subtree containing this [State]
/// object into another part of the tree (e.g., if the subtree containing this /// object is grafted from one location in the tree to another). If that
/// [State] object is grafted from one location in the tree to another). If /// happens, the framework will ensure that it calls [reactivate] to give the
/// that happens, the framework will ensure that it calls [build] to give the /// [State] object a chance to adapt to its new location in the tree. If the
/// [State] object a chance to adapt to its new location in the tree. If /// framework does reinsert this subtree, it will do so before the end of the
/// the framework does reinsert this subtree, it will do so before the end of /// animation frame in which the subtree was removed from the tree. For this
/// the animation frame in which the subtree was removed from the tree. For /// reason, [State] objects can defer releasing most resources until the
/// this reason, [State] objects can defer releasing most resources until the
/// framework calls their [dispose] method. /// framework calls their [dispose] method.
/// ///
/// Subclasses should override this method to clean up any links between /// Subclasses should override this method to clean up any links between
...@@ -1136,7 +1142,35 @@ abstract class State<T extends StatefulWidget> with Diagnosticable { ...@@ -1136,7 +1142,35 @@ abstract class State<T extends StatefulWidget> with Diagnosticable {
/// from the tree permanently. /// from the tree permanently.
@protected @protected
@mustCallSuper @mustCallSuper
void deactivate() { } void deactivate() {
assert(() {
_debugActive = !_debugActive;
return !_debugActive;
}());
}
/// Called when this object is reactivated.
///
/// If the [widget] or one of its ancestors has a [GlobalKey], the framework
/// will mark this object as inactive when it is removed and call
/// [deactivate].
///
/// If the object is reinserted to the tree in the next frame (e.g. by
/// changing position), it will be marked as active again and this method will be
/// called.
///
/// See also:
///
/// * [Element.activate] and [Element.deactivate] for more information about
/// lifecycle.
@protected
@mustCallSuper
void reactivate() {
assert(() {
_debugActive = !_debugActive;
return _debugActive;
}());
}
/// Called when this object is removed from the tree permanently. /// Called when this object is removed from the tree permanently.
/// ///
...@@ -4777,6 +4811,7 @@ class StatefulElement extends ComponentElement { ...@@ -4777,6 +4811,7 @@ class StatefulElement extends ComponentElement {
@override @override
void activate() { void activate() {
super.activate(); super.activate();
state.reactivate();
// Since the State could have observed the deactivate() and thus disposed of // Since the State could have observed the deactivate() and thus disposed of
// resources allocated in the build method, we have to rebuild the widget // resources allocated in the build method, we have to rebuild the widget
// so that its State can reallocate its resources. // so that its State can reallocate its resources.
......
...@@ -431,4 +431,67 @@ void main() { ...@@ -431,4 +431,67 @@ void main() {
throw 'Expected: paint.color.alpha == 0, found: ${paint.color.alpha}'; throw 'Expected: paint.color.alpha == 0, found: ${paint.color.alpha}';
})); }));
}); });
// Regression test for https://github.com/flutter/flutter/issues/6751
testWidgets('When Ink has a GlobalKey and changes position, splash should not stop', (WidgetTester tester) async {
const Color color = Colors.blue;
const Color splashColor = Colors.green;
void expectTest(bool painted) {
final PaintPattern paintPattern = paints
..rect(color: Color(color.value))
..circle(color: Color(splashColor.value));
expect(
Material.of(tester.element(find.byType(InkWell)))! as RenderBox,
painted ? paintPattern : isNot(paintPattern),
);
}
bool wrap = false;
final Key globalKey = GlobalKey();
await tester.pumpWidget(Directionality(
textDirection: TextDirection.ltr,
child: Material(
child: Center(
child: StatefulBuilder(
builder: (BuildContext context, void Function(void Function()) setState) {
Widget child = Ink(
key: globalKey,
color: color,
width: 200.0,
height: 200.0,
child: InkWell(
splashColor: splashColor,
onTap: () { },
onTapDown: (_) async {
await Future<void>.delayed(const Duration(milliseconds: 50));
setState(() {
wrap = !wrap;
});
}
),
);
if (wrap) {
child = Container(
margin: const EdgeInsets.only(top: 320),
child: child,
);
}
return child;
}
),
),
),
));
final TestGesture testGesture = await tester.startGesture(tester.getRect(find.byType(InkWell)).center);
await tester.pump(const Duration(milliseconds: 60));
expectTest(true);
await testGesture.up();
await tester.pumpAndSettle();
expectTest(false);
});
} }
...@@ -1331,24 +1331,28 @@ void main() { ...@@ -1331,24 +1331,28 @@ void main() {
expect(key.currentState, isNotNull); expect(key.currentState, isNotNull);
expect(state.didChangeDependenciesCount, 1); expect(state.didChangeDependenciesCount, 1);
expect(state.deactivatedCount, 0); expect(state.deactivatedCount, 0);
expect(state.reactivatedCount, 0);
/// Rebuild with updated value - should call didChangeDependencies /// Rebuild with updated value - should call didChangeDependencies
await tester.pumpWidget(Inherited(2, child: DependentStatefulWidget(key: key))); await tester.pumpWidget(Inherited(2, child: DependentStatefulWidget(key: key)));
expect(key.currentState, isNotNull); expect(key.currentState, isNotNull);
expect(state.didChangeDependenciesCount, 2); expect(state.didChangeDependenciesCount, 2);
expect(state.deactivatedCount, 0); expect(state.deactivatedCount, 0);
expect(state.reactivatedCount, 0);
// reparent it - should call deactivate and didChangeDependencies // reparent it - should call deactivate, reactivate, didChangeDependencies
await tester.pumpWidget(Inherited(3, child: SizedBox(child: DependentStatefulWidget(key: key)))); await tester.pumpWidget(Inherited(3, child: SizedBox(child: DependentStatefulWidget(key: key))));
expect(key.currentState, isNotNull); expect(key.currentState, isNotNull);
expect(state.didChangeDependenciesCount, 3); expect(state.didChangeDependenciesCount, 3);
expect(state.deactivatedCount, 1); expect(state.deactivatedCount, 1);
expect(state.reactivatedCount, 1);
// Remove it - should call deactivate, but not didChangeDependencies // Remove it - should call deactivate, but not reactivate or didChangeDependencies
await tester.pumpWidget(const Inherited(4, child: SizedBox())); await tester.pumpWidget(const Inherited(4, child: SizedBox()));
expect(key.currentState, isNull); expect(key.currentState, isNull);
expect(state.didChangeDependenciesCount, 3); expect(state.didChangeDependenciesCount, 3);
expect(state.deactivatedCount, 2); expect(state.deactivatedCount, 2);
expect(state.reactivatedCount, 1);
}); });
testWidgets('StatefulElement subclass can decorate State.build', (WidgetTester tester) async { testWidgets('StatefulElement subclass can decorate State.build', (WidgetTester tester) async {
...@@ -1391,17 +1395,21 @@ void main() { ...@@ -1391,17 +1395,21 @@ void main() {
expect(debugDoingBuildOnBuild, isTrue); expect(debugDoingBuildOnBuild, isTrue);
}); });
testWidgets('StatefulWidget', (WidgetTester tester) async { testWidgets('StatefulWidget', (WidgetTester tester) async {
final Key key = GlobalKey();
late bool debugDoingBuildOnBuild; late bool debugDoingBuildOnBuild;
late bool debugDoingBuildOnInitState; late bool debugDoingBuildOnInitState;
late bool debugDoingBuildOnDidChangeDependencies; late bool debugDoingBuildOnDidChangeDependencies;
late bool debugDoingBuildOnDidUpdateWidget; late bool debugDoingBuildOnDidUpdateWidget;
bool? debugDoingBuildOnDispose; bool? debugDoingBuildOnDispose;
bool? debugDoingBuildOnDeactivate; bool? debugDoingBuildOnDeactivate;
bool? debugDoingBuildOnReactivate;
await tester.pumpWidget( await tester.pumpWidget(
Inherited( Inherited(
0, 0,
child: StatefulWidgetSpy( child: StatefulWidgetSpy(
key: key,
onInitState: (BuildContext context) { onInitState: (BuildContext context) {
debugDoingBuildOnInitState = context.debugDoingBuild; debugDoingBuildOnInitState = context.debugDoingBuild;
}, },
...@@ -1427,6 +1435,7 @@ void main() { ...@@ -1427,6 +1435,7 @@ void main() {
Inherited( Inherited(
1, 1,
child: StatefulWidgetSpy( child: StatefulWidgetSpy(
key: key,
onDidUpdateWidget: (BuildContext context) { onDidUpdateWidget: (BuildContext context) {
debugDoingBuildOnDidUpdateWidget = context.debugDoingBuild; debugDoingBuildOnDidUpdateWidget = context.debugDoingBuild;
}, },
...@@ -1442,6 +1451,9 @@ void main() { ...@@ -1442,6 +1451,9 @@ void main() {
onDeactivate: (BuildContext context) { onDeactivate: (BuildContext context) {
debugDoingBuildOnDeactivate = context.debugDoingBuild; debugDoingBuildOnDeactivate = context.debugDoingBuild;
}, },
onReactivate: (BuildContext context) {
debugDoingBuildOnReactivate = context.debugDoingBuild;
},
), ),
), ),
); );
...@@ -1451,6 +1463,35 @@ void main() { ...@@ -1451,6 +1463,35 @@ void main() {
expect(debugDoingBuildOnDidUpdateWidget, isFalse); expect(debugDoingBuildOnDidUpdateWidget, isFalse);
expect(debugDoingBuildOnDidChangeDependencies, isFalse); expect(debugDoingBuildOnDidChangeDependencies, isFalse);
expect(debugDoingBuildOnDeactivate, isNull); expect(debugDoingBuildOnDeactivate, isNull);
expect(debugDoingBuildOnReactivate, isNull);
expect(debugDoingBuildOnDispose, isNull);
await tester.pumpWidget(
Inherited(
1,
child: SizedBox(
child: StatefulWidgetSpy(
key: key,
onBuild: (BuildContext context) {
debugDoingBuildOnBuild = context.debugDoingBuild;
},
onDispose: (BuildContext context) {
debugDoingBuildOnDispose = context.debugDoingBuild;
},
onDeactivate: (BuildContext context) {
debugDoingBuildOnDeactivate = context.debugDoingBuild;
},
onReactivate: (BuildContext context) {
debugDoingBuildOnReactivate = context.debugDoingBuild;
},
),
),
),
);
expect(debugDoingBuildOnBuild, isTrue);
expect(debugDoingBuildOnDeactivate, isFalse);
expect(debugDoingBuildOnReactivate, isFalse);
expect(debugDoingBuildOnDispose, isNull); expect(debugDoingBuildOnDispose, isNull);
await tester.pumpWidget(Container()); await tester.pumpWidget(Container());
...@@ -1705,6 +1746,7 @@ class DependentStatefulWidget extends StatefulWidget { ...@@ -1705,6 +1746,7 @@ class DependentStatefulWidget extends StatefulWidget {
class DependentState extends State<DependentStatefulWidget> { class DependentState extends State<DependentStatefulWidget> {
int didChangeDependenciesCount = 0; int didChangeDependenciesCount = 0;
int deactivatedCount = 0; int deactivatedCount = 0;
int reactivatedCount = 0;
@override @override
void didChangeDependencies() { void didChangeDependencies() {
...@@ -1723,6 +1765,12 @@ class DependentState extends State<DependentStatefulWidget> { ...@@ -1723,6 +1765,12 @@ class DependentState extends State<DependentStatefulWidget> {
super.deactivate(); super.deactivate();
deactivatedCount += 1; deactivatedCount += 1;
} }
@override
void reactivate() {
super.reactivate();
reactivatedCount += 1;
}
} }
class SwapKeyWidget extends StatefulWidget { class SwapKeyWidget extends StatefulWidget {
...@@ -1810,6 +1858,7 @@ class StatefulWidgetSpy extends StatefulWidget { ...@@ -1810,6 +1858,7 @@ class StatefulWidgetSpy extends StatefulWidget {
this.onDidChangeDependencies, this.onDidChangeDependencies,
this.onDispose, this.onDispose,
this.onDeactivate, this.onDeactivate,
this.onReactivate,
this.onDidUpdateWidget, this.onDidUpdateWidget,
}) : super(key: key); }) : super(key: key);
...@@ -1818,6 +1867,7 @@ class StatefulWidgetSpy extends StatefulWidget { ...@@ -1818,6 +1867,7 @@ class StatefulWidgetSpy extends StatefulWidget {
final void Function(BuildContext)? onDidChangeDependencies; final void Function(BuildContext)? onDidChangeDependencies;
final void Function(BuildContext)? onDispose; final void Function(BuildContext)? onDispose;
final void Function(BuildContext)? onDeactivate; final void Function(BuildContext)? onDeactivate;
final void Function(BuildContext)? onReactivate;
final void Function(BuildContext)? onDidUpdateWidget; final void Function(BuildContext)? onDidUpdateWidget;
@override @override
...@@ -1837,6 +1887,12 @@ class _StatefulWidgetSpyState extends State<StatefulWidgetSpy> { ...@@ -1837,6 +1887,12 @@ class _StatefulWidgetSpyState extends State<StatefulWidgetSpy> {
widget.onDeactivate?.call(context); widget.onDeactivate?.call(context);
} }
@override
void reactivate() {
super.reactivate();
widget.onReactivate?.call(context);
}
@override @override
void dispose() { void dispose() {
super.dispose(); super.dispose();
......
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