Skip to content

Commit d35671c

Browse files
authored
Fix FlutterError.onError in debug mode (flutter#53843)
* Fix FlutterError.onError in debug mode * update * fix comments * add license header * fix analyzer * update * another attempt * fix test * fix comment
1 parent 900c7c1 commit d35671c

File tree

7 files changed

+115
-11
lines changed

7 files changed

+115
-11
lines changed

packages/flutter/lib/src/foundation/assertions.dart

+13-2
Original file line numberDiff line numberDiff line change
@@ -715,7 +715,7 @@ class FlutterError extends Error with DiagnosticableTreeMixin implements Asserti
715715

716716
/// Called whenever the Flutter framework catches an error.
717717
///
718-
/// The default behavior is to call [dumpErrorToConsole].
718+
/// The default behavior is to call [presentError].
719719
///
720720
/// You can set this to your own function to override this default behavior.
721721
/// For example, you could report all errors to your server.
@@ -725,7 +725,18 @@ class FlutterError extends Error with DiagnosticableTreeMixin implements Asserti
725725
///
726726
/// Set this to null to silently catch and ignore errors. This is not
727727
/// recommended.
728-
static FlutterExceptionHandler onError = dumpErrorToConsole;
728+
static FlutterExceptionHandler onError = (FlutterErrorDetails details) => presentError(details);
729+
730+
/// Called whenever the Flutter framework wants to present an error to the
731+
/// users.
732+
///
733+
/// The default behavior is to call [dumpErrorToConsole].
734+
///
735+
/// Plugins can override how an error is to be presented to the user. For
736+
/// example, the structured errors service extension sets its own method when
737+
/// the extension is enabled. If you want to change how Flutter responds to an
738+
/// error, use [onError] instead.
739+
static FlutterExceptionHandler presentError = dumpErrorToConsole;
729740

730741
static int _errorCount = 0;
731742

packages/flutter/lib/src/widgets/widget_inspector.dart

+3-3
Original file line numberDiff line numberDiff line change
@@ -959,13 +959,13 @@ mixin WidgetInspectorService {
959959
SchedulerBinding.instance.addPersistentFrameCallback(_onFrameStart);
960960

961961
final FlutterExceptionHandler structuredExceptionHandler = _reportError;
962-
final FlutterExceptionHandler defaultExceptionHandler = FlutterError.onError;
962+
final FlutterExceptionHandler defaultExceptionHandler = FlutterError.presentError;
963963

964964
_registerBoolServiceExtension(
965965
name: 'structuredErrors',
966-
getter: () async => FlutterError.onError == structuredExceptionHandler,
966+
getter: () async => FlutterError.presentError == structuredExceptionHandler,
967967
setter: (bool value) {
968-
FlutterError.onError = value ? structuredExceptionHandler : defaultExceptionHandler;
968+
FlutterError.presentError = value ? structuredExceptionHandler : defaultExceptionHandler;
969969
return Future<void>.value();
970970
},
971971
);

packages/flutter/test/material/scaffold_test.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -1840,7 +1840,7 @@ void main() {
18401840
final GlobalKey<ScaffoldState> key = GlobalKey<ScaffoldState>();
18411841
const Key buttonKey = Key('button');
18421842
final List<FlutterErrorDetails> errors = <FlutterErrorDetails>[];
1843-
FlutterError.onError = (FlutterErrorDetails error) => errors.add(error);
1843+
FlutterError.presentError = (FlutterErrorDetails error) => errors.add(error);
18441844
int state = 0;
18451845
await tester.pumpWidget(
18461846
MaterialApp(

packages/flutter/test/widgets/image_test.dart

+2
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,7 @@ void main() {
528528
final ImageListener listener = (ImageInfo info, bool synchronous) {
529529
capturedImage = info;
530530
};
531+
final FlutterExceptionHandler oldHandler = FlutterError.onError;
531532
FlutterError.onError = (FlutterErrorDetails flutterError) {
532533
reportedException = flutterError.exception;
533534
reportedStackTrace = flutterError.stack;
@@ -564,6 +565,7 @@ void main() {
564565
// The image stream error handler should have the original exception.
565566
expect(capturedException, testException);
566567
expect(capturedStackTrace, testStack);
568+
FlutterError.onError = oldHandler;
567569
});
568570

569571
testWidgets('Duplicate listener registration does not affect error listeners', (WidgetTester tester) async {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:async';
6+
import 'dart:convert';
7+
8+
import 'package:flutter/foundation.dart';
9+
import 'package:flutter/rendering.dart';
10+
import 'package:flutter/widgets.dart';
11+
import 'package:flutter_test/flutter_test.dart';
12+
13+
void main() {
14+
StructureErrorTestWidgetInspectorService.runTests();
15+
}
16+
17+
typedef InspectorServiceExtensionCallback = FutureOr<Map<String, Object>> Function(Map<String, String> parameters);
18+
19+
class StructureErrorTestWidgetInspectorService extends Object with WidgetInspectorService {
20+
final Map<String, InspectorServiceExtensionCallback> extensions = <String, InspectorServiceExtensionCallback>{};
21+
22+
final Map<String, List<Map<Object, Object>>> eventsDispatched = <String, List<Map<Object, Object>>>{};
23+
24+
@override
25+
void registerServiceExtension({
26+
@required String name,
27+
@required FutureOr<Map<String, Object>> callback(Map<String, String> parameters),
28+
}) {
29+
assert(!extensions.containsKey(name));
30+
extensions[name] = callback;
31+
}
32+
33+
@override
34+
void postEvent(String eventKind, Map<Object, Object> eventData) {
35+
getEventsDispatched(eventKind).add(eventData);
36+
}
37+
38+
List<Map<Object, Object>> getEventsDispatched(String eventKind) {
39+
return eventsDispatched.putIfAbsent(eventKind, () => <Map<Object, Object>>[]);
40+
}
41+
42+
Iterable<Map<Object, Object>> getServiceExtensionStateChangedEvents(String extensionName) {
43+
return getEventsDispatched('Flutter.ServiceExtensionStateChanged')
44+
.where((Map<Object, Object> event) => event['extension'] == extensionName);
45+
}
46+
47+
Future<String> testBoolExtension(String name, Map<String, String> arguments) async {
48+
expect(extensions, contains(name));
49+
// Encode and decode to JSON to match behavior using a real service
50+
// extension where only JSON is allowed.
51+
return json.decode(json.encode(await extensions[name](arguments)))['enabled'] as String;
52+
}
53+
54+
55+
static void runTests() {
56+
final StructureErrorTestWidgetInspectorService service = StructureErrorTestWidgetInspectorService();
57+
WidgetInspectorService.instance = service;
58+
59+
test('ext.flutter.inspector.structuredErrors still report error to original on error', () async {
60+
final FlutterExceptionHandler oldHandler = FlutterError.onError;
61+
62+
FlutterErrorDetails actualError;
63+
// Creates a spy onError. This spy needs to be set before widgets binding
64+
// initializes.
65+
FlutterError.onError = (FlutterErrorDetails details) {
66+
actualError = details;
67+
};
68+
69+
WidgetsFlutterBinding.ensureInitialized();
70+
try {
71+
// Enables structured errors.
72+
expect(await service.testBoolExtension(
73+
'structuredErrors', <String, String>{'enabled': 'true'}),
74+
equals('true'));
75+
76+
// Creates an error.
77+
final FlutterErrorDetails expectedError = FlutterErrorDetailsForRendering(
78+
library: 'rendering library',
79+
context: ErrorDescription('during layout'),
80+
exception: StackTrace.current,
81+
);
82+
FlutterError.reportError(expectedError);
83+
84+
// Validates the spy still received an error.
85+
expect(actualError, expectedError);
86+
} finally {
87+
FlutterError.onError = oldHandler;
88+
}
89+
});
90+
}
91+
}

packages/flutter/test/widgets/widget_inspector_test.dart

+2-2
Original file line numberDiff line numberDiff line change
@@ -2280,7 +2280,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
22802280
List<Map<Object, Object>> flutterErrorEvents = service.getEventsDispatched('Flutter.Error');
22812281
expect(flutterErrorEvents, isEmpty);
22822282

2283-
final FlutterExceptionHandler oldHandler = FlutterError.onError;
2283+
final FlutterExceptionHandler oldHandler = FlutterError.presentError;
22842284

22852285
try {
22862286
// Enable structured errors.
@@ -2337,7 +2337,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
23372337
error = flutterErrorEvents.last;
23382338
expect(error['errorsSinceReload'], 0);
23392339
} finally {
2340-
FlutterError.onError = oldHandler;
2340+
FlutterError.presentError = oldHandler;
23412341
}
23422342
});
23432343

packages/flutter_test/lib/src/binding.dart

+3-3
Original file line numberDiff line numberDiff line change
@@ -572,9 +572,9 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
572572
}) {
573573
assert(description != null);
574574
assert(inTest);
575-
_oldExceptionHandler = FlutterError.onError;
575+
_oldExceptionHandler = FlutterError.presentError;
576576
int _exceptionCount = 0; // number of un-taken exceptions
577-
FlutterError.onError = (FlutterErrorDetails details) {
577+
FlutterError.presentError = (FlutterErrorDetails details) {
578578
if (_pendingExceptionDetails != null) {
579579
debugPrint = debugPrintOverride; // just in case the test overrides it -- otherwise we won't see the errors!
580580
if (_exceptionCount == 0) {
@@ -800,7 +800,7 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
800800
/// Called by the [testWidgets] function after a test is executed.
801801
void postTest() {
802802
assert(inTest);
803-
FlutterError.onError = _oldExceptionHandler;
803+
FlutterError.presentError = _oldExceptionHandler;
804804
_pendingExceptionDetails = null;
805805
_parentZone = null;
806806
buildOwner.focusManager = FocusManager();

0 commit comments

Comments
 (0)