mirror of
https://github.com/flutter/flutter
synced 2024-10-04 07:19:46 +00:00
Fix reentrancy with WidgetBindingObserver callbacks (#131774)
Part of https://github.com/flutter/flutter/issues/131678 Fixes up callsites for WidgetsBindingObserver related callbacks.
This commit is contained in:
parent
aff8ef13d4
commit
b3f99ffe61
|
@ -623,7 +623,7 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
|
|||
@override
|
||||
Future<AppExitResponse> handleRequestAppExit() async {
|
||||
bool didCancel = false;
|
||||
for (final WidgetsBindingObserver observer in _observers) {
|
||||
for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.of(_observers)) {
|
||||
if ((await observer.didRequestAppExit()) == AppExitResponse.cancel) {
|
||||
didCancel = true;
|
||||
// Don't early return. For the case where someone is just using the
|
||||
|
@ -637,7 +637,7 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
|
|||
@override
|
||||
void handleMetricsChanged() {
|
||||
super.handleMetricsChanged();
|
||||
for (final WidgetsBindingObserver observer in _observers) {
|
||||
for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.of(_observers)) {
|
||||
observer.didChangeMetrics();
|
||||
}
|
||||
}
|
||||
|
@ -645,7 +645,7 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
|
|||
@override
|
||||
void handleTextScaleFactorChanged() {
|
||||
super.handleTextScaleFactorChanged();
|
||||
for (final WidgetsBindingObserver observer in _observers) {
|
||||
for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.of(_observers)) {
|
||||
observer.didChangeTextScaleFactor();
|
||||
}
|
||||
}
|
||||
|
@ -653,7 +653,7 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
|
|||
@override
|
||||
void handlePlatformBrightnessChanged() {
|
||||
super.handlePlatformBrightnessChanged();
|
||||
for (final WidgetsBindingObserver observer in _observers) {
|
||||
for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.of(_observers)) {
|
||||
observer.didChangePlatformBrightness();
|
||||
}
|
||||
}
|
||||
|
@ -661,7 +661,7 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
|
|||
@override
|
||||
void handleAccessibilityFeaturesChanged() {
|
||||
super.handleAccessibilityFeaturesChanged();
|
||||
for (final WidgetsBindingObserver observer in _observers) {
|
||||
for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.of(_observers)) {
|
||||
observer.didChangeAccessibilityFeatures();
|
||||
}
|
||||
}
|
||||
|
@ -673,6 +673,7 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
|
|||
/// See [dart:ui.PlatformDispatcher.onLocaleChanged].
|
||||
@protected
|
||||
@mustCallSuper
|
||||
@visibleForTesting
|
||||
void handleLocaleChanged() {
|
||||
dispatchLocalesChanged(platformDispatcher.locales);
|
||||
}
|
||||
|
@ -686,7 +687,7 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
|
|||
@protected
|
||||
@mustCallSuper
|
||||
void dispatchLocalesChanged(List<Locale>? locales) {
|
||||
for (final WidgetsBindingObserver observer in _observers) {
|
||||
for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.of(_observers)) {
|
||||
observer.didChangeLocales(locales);
|
||||
}
|
||||
}
|
||||
|
@ -700,7 +701,7 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
|
|||
@protected
|
||||
@mustCallSuper
|
||||
void dispatchAccessibilityFeaturesChanged() {
|
||||
for (final WidgetsBindingObserver observer in _observers) {
|
||||
for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.of(_observers)) {
|
||||
observer.didChangeAccessibilityFeatures();
|
||||
}
|
||||
}
|
||||
|
@ -720,6 +721,7 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
|
|||
/// This method exposes the `popRoute` notification from
|
||||
/// [SystemChannels.navigation].
|
||||
@protected
|
||||
@visibleForTesting
|
||||
Future<void> handlePopRoute() async {
|
||||
for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.of(_observers)) {
|
||||
if (await observer.didPopRoute()) {
|
||||
|
@ -741,6 +743,7 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
|
|||
/// [SystemChannels.navigation].
|
||||
@protected
|
||||
@mustCallSuper
|
||||
@visibleForTesting
|
||||
Future<void> handlePushRoute(String route) async {
|
||||
final RouteInformation routeInformation = RouteInformation(uri: Uri.parse(route));
|
||||
for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.of(_observers)) {
|
||||
|
@ -777,7 +780,7 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
|
|||
@override
|
||||
void handleAppLifecycleStateChanged(AppLifecycleState state) {
|
||||
super.handleAppLifecycleStateChanged(state);
|
||||
for (final WidgetsBindingObserver observer in _observers) {
|
||||
for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.of(_observers)) {
|
||||
observer.didChangeAppLifecycleState(state);
|
||||
}
|
||||
}
|
||||
|
@ -785,7 +788,7 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
|
|||
@override
|
||||
void handleMemoryPressure() {
|
||||
super.handleMemoryPressure();
|
||||
for (final WidgetsBindingObserver observer in _observers) {
|
||||
for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.of(_observers)) {
|
||||
observer.didHaveMemoryPressure();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
@ -45,6 +47,94 @@ class PushRouteInformationObserver with WidgetsBindingObserver {
|
|||
}
|
||||
}
|
||||
|
||||
// Implements to make sure all methods get coverage.
|
||||
class RentrantObserver implements WidgetsBindingObserver {
|
||||
RentrantObserver() {
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
}
|
||||
|
||||
bool active = true;
|
||||
|
||||
int removeSelf() {
|
||||
active = false;
|
||||
int count = 0;
|
||||
while (WidgetsBinding.instance.removeObserver(this)) {
|
||||
count += 1;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAccessibilityFeatures() {
|
||||
assert(active);
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
assert(active);
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeLocales(List<Locale>? locales) {
|
||||
assert(active);
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeMetrics() {
|
||||
assert(active);
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangePlatformBrightness() {
|
||||
assert(active);
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeTextScaleFactor() {
|
||||
assert(active);
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
}
|
||||
|
||||
@override
|
||||
void didHaveMemoryPressure() {
|
||||
assert(active);
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> didPopRoute() {
|
||||
assert(active);
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
return Future<bool>.value(true);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> didPushRoute(String route) {
|
||||
assert(active);
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
return Future<bool>.value(true);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> didPushRouteInformation(RouteInformation routeInformation) {
|
||||
assert(active);
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
return Future<bool>.value(true);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<AppExitResponse> didRequestAppExit() {
|
||||
assert(active);
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
return Future<AppExitResponse>.value(AppExitResponse.exit);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
Future<void> setAppLifeCycleState(AppLifecycleState state) async {
|
||||
final ByteData? message =
|
||||
|
@ -53,6 +143,23 @@ void main() {
|
|||
.handlePlatformMessage('flutter/lifecycle', message, (_) { });
|
||||
}
|
||||
|
||||
testWidgets('Rentrant observer callbacks do not result in exceptions', (WidgetTester tester) async {
|
||||
final RentrantObserver observer = RentrantObserver();
|
||||
WidgetsBinding.instance.handleAccessibilityFeaturesChanged();
|
||||
WidgetsBinding.instance.handleAppLifecycleStateChanged(AppLifecycleState.resumed);
|
||||
WidgetsBinding.instance.handleLocaleChanged();
|
||||
WidgetsBinding.instance.handleMetricsChanged();
|
||||
WidgetsBinding.instance.handlePlatformBrightnessChanged();
|
||||
WidgetsBinding.instance.handleTextScaleFactorChanged();
|
||||
WidgetsBinding.instance.handleMemoryPressure();
|
||||
WidgetsBinding.instance.handlePopRoute();
|
||||
WidgetsBinding.instance.handlePushRoute('/');
|
||||
WidgetsBinding.instance.handleRequestAppExit();
|
||||
await tester.idle();
|
||||
expect(observer.removeSelf(), greaterThan(1));
|
||||
expect(observer.removeSelf(), 0);
|
||||
});
|
||||
|
||||
testWidgets('didHaveMemoryPressure callback', (WidgetTester tester) async {
|
||||
final MemoryPressureObserver observer = MemoryPressureObserver();
|
||||
WidgetsBinding.instance.addObserver(observer);
|
||||
|
@ -118,6 +225,7 @@ void main() {
|
|||
|
||||
observer.accumulatedStates.clear();
|
||||
await expectLater(() async => setAppLifeCycleState(AppLifecycleState.detached), throwsAssertionError);
|
||||
WidgetsBinding.instance.removeObserver(observer);
|
||||
});
|
||||
|
||||
testWidgets('didPushRoute callback', (WidgetTester tester) async {
|
||||
|
|
Loading…
Reference in a new issue