Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: Multiple FlutterError.onError calls in FlutterErrorIntegration #345

Merged
merged 9 commits into from
Mar 10, 2021
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Unreleased

* Bump: sentry-android to v4.3.0 (#343)
* Fix: Multiple FlutterError.onError calls in FlutterErrorIntegration (#345)

# 4.1.0-nullsafety.0

Expand Down
17 changes: 15 additions & 2 deletions flutter/lib/src/default_integrations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,14 @@ class WidgetsFlutterBindingIntegration
/// and are stripped in release mode. See [Flutter build modes](https://flutter.dev/docs/testing/build-modes).
/// So they only get caught in debug mode.
class FlutterErrorIntegration extends Integration<SentryFlutterOptions> {
/// Keep a reference to the original handler.
static FlutterExceptionHandler? defaultOnError;
denrase marked this conversation as resolved.
Show resolved Hide resolved

@override
void call(Hub hub, SentryFlutterOptions options) {
final defaultOnError = FlutterError.onError;
if (defaultOnError == null && FlutterError.onError != null) {
defaultOnError = FlutterError.onError;
}

FlutterError.onError = (FlutterErrorDetails errorDetails) async {
dynamic exception = errorDetails.exception;
Expand All @@ -60,7 +65,7 @@ class FlutterErrorIntegration extends Integration<SentryFlutterOptions> {

// call original handler
if (defaultOnError != null) {
defaultOnError(errorDetails);
defaultOnError!(errorDetails);
}

// we don't call Zone.current.handleUncaughtError because we'd like
Expand All @@ -76,6 +81,14 @@ class FlutterErrorIntegration extends Integration<SentryFlutterOptions> {

options.sdk.addIntegration('flutterErrorIntegration');
}

@override
void close() {
/// Restore default
FlutterError.onError = defaultOnError;
denrase marked this conversation as resolved.
Show resolved Hide resolved
defaultOnError = null;
denrase marked this conversation as resolved.
Show resolved Hide resolved
super.close();
denrase marked this conversation as resolved.
Show resolved Hide resolved
}
}

/// Load Device's Contexts from the iOS SDK.
Expand Down
36 changes: 36 additions & 0 deletions flutter/test/default_integrations_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ void main() {

tearDown(() {
_channel.setMockMethodCallHandler(null);
FlutterErrorIntegration.defaultOnError = null;
denrase marked this conversation as resolved.
Show resolved Hide resolved
});

void _reportError({
Expand Down Expand Up @@ -75,6 +76,41 @@ void main() {
expect(true, called);
});

test('FlutterErrorIntegration captureEvent only called once', () async {
var numberOfDefaultCalls = 0;
final defaultError = (FlutterErrorDetails errorDetails) async {
numberOfDefaultCalls++;
};
FlutterError.onError = defaultError;

when(fixture.hub.captureEvent(captureAny))
.thenAnswer((_) => Future.value(SentryId.empty()));

final details = FlutterErrorDetails(exception: StateError('error'));

final errorIntegration = FlutterErrorIntegration();
errorIntegration.call(fixture.hub, fixture.options);
errorIntegration.call(fixture.hub, fixture.options);
denrase marked this conversation as resolved.
Show resolved Hide resolved

FlutterError.reportError(details);

verify(await fixture.hub.captureEvent(captureAny)).called(1);
expect(numberOfDefaultCalls, 1);
});

test('FlutterErrorIntegration close restored default onError', () async {
denrase marked this conversation as resolved.
Show resolved Hide resolved
final defaultOnError = (FlutterErrorDetails errorDetails) async {};
FlutterError.onError = defaultOnError;

final integrtion = FlutterErrorIntegration();
integrtion.call(fixture.hub, fixture.options);
expect(false, defaultOnError == FlutterError.onError);

integrtion.close();

expect(true, defaultOnError == FlutterError.onError);
denrase marked this conversation as resolved.
Show resolved Hide resolved
});

test('FlutterError do not capture if silent error', () async {
_reportError(silent: true);

Expand Down