mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +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
|
@override
|
||||||
Future<AppExitResponse> handleRequestAppExit() async {
|
Future<AppExitResponse> handleRequestAppExit() async {
|
||||||
bool didCancel = false;
|
bool didCancel = false;
|
||||||
for (final WidgetsBindingObserver observer in _observers) {
|
for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.of(_observers)) {
|
||||||
if ((await observer.didRequestAppExit()) == AppExitResponse.cancel) {
|
if ((await observer.didRequestAppExit()) == AppExitResponse.cancel) {
|
||||||
didCancel = true;
|
didCancel = true;
|
||||||
// Don't early return. For the case where someone is just using the
|
// 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
|
@override
|
||||||
void handleMetricsChanged() {
|
void handleMetricsChanged() {
|
||||||
super.handleMetricsChanged();
|
super.handleMetricsChanged();
|
||||||
for (final WidgetsBindingObserver observer in _observers) {
|
for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.of(_observers)) {
|
||||||
observer.didChangeMetrics();
|
observer.didChangeMetrics();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -645,7 +645,7 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
|
|||||||
@override
|
@override
|
||||||
void handleTextScaleFactorChanged() {
|
void handleTextScaleFactorChanged() {
|
||||||
super.handleTextScaleFactorChanged();
|
super.handleTextScaleFactorChanged();
|
||||||
for (final WidgetsBindingObserver observer in _observers) {
|
for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.of(_observers)) {
|
||||||
observer.didChangeTextScaleFactor();
|
observer.didChangeTextScaleFactor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -653,7 +653,7 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
|
|||||||
@override
|
@override
|
||||||
void handlePlatformBrightnessChanged() {
|
void handlePlatformBrightnessChanged() {
|
||||||
super.handlePlatformBrightnessChanged();
|
super.handlePlatformBrightnessChanged();
|
||||||
for (final WidgetsBindingObserver observer in _observers) {
|
for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.of(_observers)) {
|
||||||
observer.didChangePlatformBrightness();
|
observer.didChangePlatformBrightness();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -661,7 +661,7 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
|
|||||||
@override
|
@override
|
||||||
void handleAccessibilityFeaturesChanged() {
|
void handleAccessibilityFeaturesChanged() {
|
||||||
super.handleAccessibilityFeaturesChanged();
|
super.handleAccessibilityFeaturesChanged();
|
||||||
for (final WidgetsBindingObserver observer in _observers) {
|
for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.of(_observers)) {
|
||||||
observer.didChangeAccessibilityFeatures();
|
observer.didChangeAccessibilityFeatures();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -673,6 +673,7 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
|
|||||||
/// See [dart:ui.PlatformDispatcher.onLocaleChanged].
|
/// See [dart:ui.PlatformDispatcher.onLocaleChanged].
|
||||||
@protected
|
@protected
|
||||||
@mustCallSuper
|
@mustCallSuper
|
||||||
|
@visibleForTesting
|
||||||
void handleLocaleChanged() {
|
void handleLocaleChanged() {
|
||||||
dispatchLocalesChanged(platformDispatcher.locales);
|
dispatchLocalesChanged(platformDispatcher.locales);
|
||||||
}
|
}
|
||||||
@ -686,7 +687,7 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
|
|||||||
@protected
|
@protected
|
||||||
@mustCallSuper
|
@mustCallSuper
|
||||||
void dispatchLocalesChanged(List<Locale>? locales) {
|
void dispatchLocalesChanged(List<Locale>? locales) {
|
||||||
for (final WidgetsBindingObserver observer in _observers) {
|
for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.of(_observers)) {
|
||||||
observer.didChangeLocales(locales);
|
observer.didChangeLocales(locales);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -700,7 +701,7 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
|
|||||||
@protected
|
@protected
|
||||||
@mustCallSuper
|
@mustCallSuper
|
||||||
void dispatchAccessibilityFeaturesChanged() {
|
void dispatchAccessibilityFeaturesChanged() {
|
||||||
for (final WidgetsBindingObserver observer in _observers) {
|
for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.of(_observers)) {
|
||||||
observer.didChangeAccessibilityFeatures();
|
observer.didChangeAccessibilityFeatures();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -720,6 +721,7 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
|
|||||||
/// This method exposes the `popRoute` notification from
|
/// This method exposes the `popRoute` notification from
|
||||||
/// [SystemChannels.navigation].
|
/// [SystemChannels.navigation].
|
||||||
@protected
|
@protected
|
||||||
|
@visibleForTesting
|
||||||
Future<void> handlePopRoute() async {
|
Future<void> handlePopRoute() async {
|
||||||
for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.of(_observers)) {
|
for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.of(_observers)) {
|
||||||
if (await observer.didPopRoute()) {
|
if (await observer.didPopRoute()) {
|
||||||
@ -741,6 +743,7 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
|
|||||||
/// [SystemChannels.navigation].
|
/// [SystemChannels.navigation].
|
||||||
@protected
|
@protected
|
||||||
@mustCallSuper
|
@mustCallSuper
|
||||||
|
@visibleForTesting
|
||||||
Future<void> handlePushRoute(String route) async {
|
Future<void> handlePushRoute(String route) async {
|
||||||
final RouteInformation routeInformation = RouteInformation(uri: Uri.parse(route));
|
final RouteInformation routeInformation = RouteInformation(uri: Uri.parse(route));
|
||||||
for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.of(_observers)) {
|
for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.of(_observers)) {
|
||||||
@ -777,7 +780,7 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
|
|||||||
@override
|
@override
|
||||||
void handleAppLifecycleStateChanged(AppLifecycleState state) {
|
void handleAppLifecycleStateChanged(AppLifecycleState state) {
|
||||||
super.handleAppLifecycleStateChanged(state);
|
super.handleAppLifecycleStateChanged(state);
|
||||||
for (final WidgetsBindingObserver observer in _observers) {
|
for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.of(_observers)) {
|
||||||
observer.didChangeAppLifecycleState(state);
|
observer.didChangeAppLifecycleState(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -785,7 +788,7 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
|
|||||||
@override
|
@override
|
||||||
void handleMemoryPressure() {
|
void handleMemoryPressure() {
|
||||||
super.handleMemoryPressure();
|
super.handleMemoryPressure();
|
||||||
for (final WidgetsBindingObserver observer in _observers) {
|
for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.of(_observers)) {
|
||||||
observer.didHaveMemoryPressure();
|
observer.didHaveMemoryPressure();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter/widgets.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() {
|
void main() {
|
||||||
Future<void> setAppLifeCycleState(AppLifecycleState state) async {
|
Future<void> setAppLifeCycleState(AppLifecycleState state) async {
|
||||||
final ByteData? message =
|
final ByteData? message =
|
||||||
@ -53,6 +143,23 @@ void main() {
|
|||||||
.handlePlatformMessage('flutter/lifecycle', message, (_) { });
|
.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 {
|
testWidgets('didHaveMemoryPressure callback', (WidgetTester tester) async {
|
||||||
final MemoryPressureObserver observer = MemoryPressureObserver();
|
final MemoryPressureObserver observer = MemoryPressureObserver();
|
||||||
WidgetsBinding.instance.addObserver(observer);
|
WidgetsBinding.instance.addObserver(observer);
|
||||||
@ -118,6 +225,7 @@ void main() {
|
|||||||
|
|
||||||
observer.accumulatedStates.clear();
|
observer.accumulatedStates.clear();
|
||||||
await expectLater(() async => setAppLifeCycleState(AppLifecycleState.detached), throwsAssertionError);
|
await expectLater(() async => setAppLifeCycleState(AppLifecycleState.detached), throwsAssertionError);
|
||||||
|
WidgetsBinding.instance.removeObserver(observer);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('didPushRoute callback', (WidgetTester tester) async {
|
testWidgets('didPushRoute callback', (WidgetTester tester) async {
|
||||||
|
Loading…
Reference in New Issue
Block a user