Failure to construct ErrorWidget for build errors does not destroy tree (#117090)
diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart
index 217828f..6aa7ba3 100644
--- a/packages/flutter/lib/src/widgets/framework.dart
+++ b/packages/flutter/lib/src/widgets/framework.dart
@@ -4711,12 +4711,15 @@
owner!._debugCurrentBuildTarget = this;
return true;
}());
- performRebuild();
- assert(() {
- assert(owner!._debugCurrentBuildTarget == this);
- owner!._debugCurrentBuildTarget = debugPreviousBuildTarget;
- return true;
- }());
+ try {
+ performRebuild();
+ } finally {
+ assert(() {
+ assert(owner!._debugCurrentBuildTarget == this);
+ owner!._debugCurrentBuildTarget = debugPreviousBuildTarget;
+ return true;
+ }());
+ }
assert(!_dirty);
}
diff --git a/packages/flutter/test/widgets/error_widget_test.dart b/packages/flutter/test/widgets/error_widget_test.dart
new file mode 100644
index 0000000..b91bfdc
--- /dev/null
+++ b/packages/flutter/test/widgets/error_widget_test.dart
@@ -0,0 +1,92 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+ testWidgets('ErrorWidget displays actual error when throwing during build', (WidgetTester tester) async {
+ final Key container = UniqueKey();
+ const String errorText = 'Oh no, there was a crash!!1';
+
+ await tester.pumpWidget(
+ Container(
+ key: container,
+ color: Colors.red,
+ padding: const EdgeInsets.all(10),
+ child: Builder(
+ builder: (BuildContext context) {
+ throw UnsupportedError(errorText);
+ },
+ ),
+ ),
+ );
+
+ expect(
+ tester.takeException(),
+ isA<UnsupportedError>().having((UnsupportedError error) => error.message, 'message', contains(errorText)),
+ );
+
+ final ErrorWidget errorWidget = tester.widget(find.byType(ErrorWidget));
+ expect(errorWidget.message, contains(errorText));
+
+ // Failure in one widget shouldn't ripple through the entire tree and effect
+ // ancestors. Those should still be in the tree.
+ expect(find.byKey(container), findsOneWidget);
+ });
+
+ testWidgets('when constructing an ErrorWidget due to a build failure throws an error, fail gracefully', (WidgetTester tester) async {
+ final Key container = UniqueKey();
+ await tester.pumpWidget(
+ Container(
+ key: container,
+ color: Colors.red,
+ padding: const EdgeInsets.all(10),
+ // This widget throws during build, which causes the construction of an
+ // ErrorWidget with the build error. However, during construction of
+ // that ErrorWidget, another error is thrown.
+ child: const MyDoubleThrowingWidget(),
+ ),
+ );
+
+ expect(
+ tester.takeException(),
+ isA<UnsupportedError>().having((UnsupportedError error) => error.message, 'message', contains(MyThrowingElement.debugFillPropertiesErrorMessage)),
+ );
+
+ final ErrorWidget errorWidget = tester.widget(find.byType(ErrorWidget));
+ expect(errorWidget.message, contains(MyThrowingElement.debugFillPropertiesErrorMessage));
+
+ // Failure in one widget shouldn't ripple through the entire tree and effect
+ // ancestors. Those should still be in the tree.
+ expect(find.byKey(container), findsOneWidget);
+ });
+}
+
+// This widget throws during its regular build and then again when the
+// ErrorWidget is constructed, which calls MyThrowingElement.debugFillProperties.
+class MyDoubleThrowingWidget extends StatelessWidget {
+ const MyDoubleThrowingWidget({super.key});
+
+ @override
+ StatelessElement createElement() => MyThrowingElement(this);
+
+ @override
+ Widget build(BuildContext context) {
+ throw UnsupportedError('You cannot build me!');
+ }
+}
+
+class MyThrowingElement extends StatelessElement {
+ MyThrowingElement(super.widget);
+
+ static const String debugFillPropertiesErrorMessage = 'Crash during debugFillProperties';
+
+ @override
+ void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+ super.debugFillProperties(properties);
+ throw UnsupportedError(debugFillPropertiesErrorMessage);
+ }
+}