Allow reuse of NavigatorObserver in Navigator.observers (#81601)
This commit is contained in:
parent
04bb954a7d
commit
efc079657b
@ -1113,9 +1113,11 @@ abstract class State<T extends StatefulWidget> with Diagnosticable {
|
||||
/// The framework calls this method whenever it removes this [State] object
|
||||
/// from the tree. In some cases, the framework will reinsert the [State]
|
||||
/// object into another part of the tree (e.g., if the subtree containing this
|
||||
/// [State] object is grafted from one location in the tree to another). If
|
||||
/// 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
|
||||
/// [State] object is grafted from one location in the tree to another due to
|
||||
/// the use of a [GlobalKey]). If that happens, the framework will call
|
||||
/// [activate] to give the [State] object a chance to reacquire any resources
|
||||
/// that it released in [deactivate]. It will then also call [build] to give
|
||||
/// the [State] object a chance to adapt to its new location in the tree. If
|
||||
/// the framework does reinsert this subtree, it will do so before the end of
|
||||
/// the animation frame in which the subtree was removed from the tree. For
|
||||
/// this reason, [State] objects can defer releasing most resources until the
|
||||
@ -1136,6 +1138,40 @@ abstract class State<T extends StatefulWidget> with Diagnosticable {
|
||||
@mustCallSuper
|
||||
void deactivate() { }
|
||||
|
||||
/// Called when this object is reinserted into the tree after having been
|
||||
/// removed via [deactivate].
|
||||
///
|
||||
/// In most cases, after a [State] object has been deactivated, it is _not_
|
||||
/// reinserted into the tree, and its [dispose] method will be called to
|
||||
/// signal that it is ready to be garbage collected.
|
||||
///
|
||||
/// In some cases, however, after a [State] object has been deactivated, the
|
||||
/// framework will reinsert it into another part of the tree (e.g., if the
|
||||
/// subtree containing this [State] object is grafted from one location in
|
||||
/// the tree to another due to the use of a [GlobalKey]). If that happens,
|
||||
/// the framework will call [activate] to give the [State] object a chance to
|
||||
/// reacquire any resources that it released in [deactivate]. It will then
|
||||
/// also call [build] to give the object a chance to adapt to its new
|
||||
/// location in the tree. If the framework does reinsert this subtree, it
|
||||
/// will do so before the end of the animation frame in which the subtree was
|
||||
/// removed from the tree. For this reason, [State] objects can defer
|
||||
/// releasing most resources until the framework calls their [dispose] method.
|
||||
///
|
||||
/// The framework does not call this method the first time a [State] object
|
||||
/// is inserted into the tree. Instead, the framework calls [initState] in
|
||||
/// that situation.
|
||||
///
|
||||
/// Implementations of this method should start with a call to the inherited
|
||||
/// method, as in `super.activate()`.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [Element.activate], the corresponding method when an element
|
||||
/// transitions from the "inactive" to the "active" lifecycle state.
|
||||
@protected
|
||||
@mustCallSuper
|
||||
void activate() { }
|
||||
|
||||
/// Called when this object is removed from the tree permanently.
|
||||
///
|
||||
/// The framework calls this method when this [State] object will never
|
||||
@ -4804,6 +4840,7 @@ class StatefulElement extends ComponentElement {
|
||||
@override
|
||||
void activate() {
|
||||
super.activate();
|
||||
state.activate();
|
||||
// Since the State could have observed the deactivate() and thus disposed of
|
||||
// resources allocated in the build method, we have to rebuild the widget
|
||||
// so that its State can reallocate its resources.
|
||||
|
@ -3628,6 +3628,22 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
|
||||
}());
|
||||
}
|
||||
|
||||
@override
|
||||
void deactivate() {
|
||||
for (final NavigatorObserver observer in _effectiveObservers)
|
||||
observer._navigator = null;
|
||||
super.deactivate();
|
||||
}
|
||||
|
||||
@override
|
||||
void activate() {
|
||||
super.activate();
|
||||
for (final NavigatorObserver observer in _effectiveObservers) {
|
||||
assert(observer.navigator == null);
|
||||
observer._navigator = this;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
assert(!_debugLocked);
|
||||
@ -3635,9 +3651,12 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
|
||||
_debugLocked = true;
|
||||
return true;
|
||||
}());
|
||||
assert(() {
|
||||
for (final NavigatorObserver observer in _effectiveObservers)
|
||||
assert(observer._navigator != this);
|
||||
return true;
|
||||
}());
|
||||
_updateHeroController(null);
|
||||
for (final NavigatorObserver observer in _effectiveObservers)
|
||||
observer._navigator = null;
|
||||
focusScopeNode.dispose();
|
||||
for (final _RouteEntry entry in _history)
|
||||
entry.dispose();
|
||||
|
@ -1589,6 +1589,40 @@ void main() {
|
||||
expect(() => element.state, throwsA(isA<TypeError>()));
|
||||
expect(() => element.widget, throwsA(isA<TypeError>()));
|
||||
}, skip: kIsWeb);
|
||||
|
||||
testWidgets('Deactivate and activate are called correctly', (WidgetTester tester) async {
|
||||
final List<String> states = <String>[];
|
||||
Widget build([Key? key]) {
|
||||
return StatefulWidgetSpy(
|
||||
key: key,
|
||||
onInitState: (BuildContext context) { states.add('initState'); },
|
||||
onDidUpdateWidget: (BuildContext context) { states.add('didUpdateWidget'); },
|
||||
onDeactivate: (BuildContext context) { states.add('deactivate'); },
|
||||
onActivate: (BuildContext context) { states.add('activate'); },
|
||||
onBuild: (BuildContext context) { states.add('build'); },
|
||||
onDispose: (BuildContext context) { states.add('dispose'); },
|
||||
);
|
||||
}
|
||||
Future<void> pumpWidget(Widget widget) {
|
||||
states.clear();
|
||||
return tester.pumpWidget(widget);
|
||||
}
|
||||
|
||||
await pumpWidget(build());
|
||||
expect(states, <String>['initState', 'build']);
|
||||
await pumpWidget(Container(child: build()));
|
||||
expect(states, <String>['deactivate', 'initState', 'build', 'dispose']);
|
||||
await pumpWidget(Container());
|
||||
expect(states, <String>['deactivate', 'dispose']);
|
||||
|
||||
final GlobalKey key = GlobalKey();
|
||||
await pumpWidget(build(key));
|
||||
expect(states, <String>['initState', 'build']);
|
||||
await pumpWidget(Container(child: build(key)));
|
||||
expect(states, <String>['deactivate', 'activate', 'didUpdateWidget', 'build']);
|
||||
await pumpWidget(Container());
|
||||
expect(states, <String>['deactivate', 'dispose']);
|
||||
});
|
||||
}
|
||||
|
||||
class _WidgetWithNoVisitChildren extends StatelessWidget {
|
||||
@ -1827,6 +1861,7 @@ class StatefulWidgetSpy extends StatefulWidget {
|
||||
this.onDidChangeDependencies,
|
||||
this.onDispose,
|
||||
this.onDeactivate,
|
||||
this.onActivate,
|
||||
this.onDidUpdateWidget,
|
||||
}) : super(key: key);
|
||||
|
||||
@ -1835,6 +1870,7 @@ class StatefulWidgetSpy extends StatefulWidget {
|
||||
final void Function(BuildContext)? onDidChangeDependencies;
|
||||
final void Function(BuildContext)? onDispose;
|
||||
final void Function(BuildContext)? onDeactivate;
|
||||
final void Function(BuildContext)? onActivate;
|
||||
final void Function(BuildContext)? onDidUpdateWidget;
|
||||
|
||||
@override
|
||||
@ -1854,6 +1890,12 @@ class _StatefulWidgetSpyState extends State<StatefulWidgetSpy> {
|
||||
widget.onDeactivate?.call(context);
|
||||
}
|
||||
|
||||
@override
|
||||
void activate() {
|
||||
super.activate();
|
||||
widget.onActivate?.call(context);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
|
@ -3560,6 +3560,42 @@ void main() {
|
||||
expect(observations[7].previous, isNull);
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('Can reuse NavigatorObserver in rebuilt tree', (WidgetTester tester) async {
|
||||
final NavigatorObserver observer = NavigatorObserver();
|
||||
Widget build([Key? key]) {
|
||||
return Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Navigator(
|
||||
key: key,
|
||||
observers: <NavigatorObserver>[observer],
|
||||
onGenerateRoute: (RouteSettings settings) {
|
||||
return PageRouteBuilder<void>(
|
||||
settings: settings,
|
||||
pageBuilder: (BuildContext _, Animation<double> __, Animation<double> ___) {
|
||||
return Container();
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Test without reinsertion
|
||||
await tester.pumpWidget(build());
|
||||
await tester.pumpWidget(Container(child: build()));
|
||||
expect(observer.navigator, tester.state<NavigatorState>(find.byType(Navigator)));
|
||||
|
||||
// Clear the tree
|
||||
await tester.pumpWidget(Container());
|
||||
expect(observer.navigator, isNull);
|
||||
|
||||
// Test with reinsertion
|
||||
final GlobalKey key = GlobalKey();
|
||||
await tester.pumpWidget(build(key));
|
||||
await tester.pumpWidget(Container(child: build(key)));
|
||||
expect(observer.navigator, tester.state<NavigatorState>(find.byType(Navigator)));
|
||||
});
|
||||
}
|
||||
|
||||
typedef AnnouncementCallBack = void Function(Route<dynamic>?);
|
||||
|
Loading…
x
Reference in New Issue
Block a user