Skip to content

Commit

Permalink
Fix: Use PlatformDispatcher.onError in Flutter 3.3 (#1039)
Browse files Browse the repository at this point in the history
Co-authored-by: Manoel Aranda Neto <5731772+marandaneto@users.noreply.github.com>
Co-authored-by: Manoel Aranda Neto <marandaneto@gmail.com>
  • Loading branch information
3 people authored Oct 13, 2022
1 parent 4efee31 commit 02419b7
Show file tree
Hide file tree
Showing 10 changed files with 366 additions and 147 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Features

- Use PlatformDispatcher.onError in Flutter 3.3 ([#1039](https://github.com/getsentry/sentry-dart/pull/1039))

### Fixes

- Bring protocol up to date with latest Sentry protocol ([#1038](https://github.com/getsentry/sentry-dart/pull/1038))
Expand Down
42 changes: 26 additions & 16 deletions dart/lib/src/sentry.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class Sentry {
static Future<void> init(
OptionsConfiguration optionsConfiguration, {
AppRunner? appRunner,
@internal bool callAppRunnerInRunZonedGuarded = true,
@internal SentryOptions? options,
}) async {
final sentryOptions = options ?? SentryOptions();
Expand All @@ -56,7 +57,7 @@ class Sentry {
throw ArgumentError('DSN is required.');
}

await _init(sentryOptions, appRunner);
await _init(sentryOptions, appRunner, callAppRunnerInRunZonedGuarded);
}

static Future<void> _initDefaultValues(
Expand Down Expand Up @@ -96,7 +97,11 @@ class Sentry {
}

/// Initializes the SDK
static Future<void> _init(SentryOptions options, AppRunner? appRunner) async {
static Future<void> _init(
SentryOptions options,
AppRunner? appRunner,
bool callAppRunnerInRunZonedGuarded,
) async {
if (isEnabled) {
options.logger(
SentryLevel.warning,
Expand All @@ -113,21 +118,26 @@ class Sentry {

// execute integrations after hub being enabled
if (appRunner != null) {
var runIntegrationsAndAppRunner = () async {
final integrations =
options.integrations.where((i) => i is! RunZonedGuardedIntegration);
await _callIntegrations(integrations, options);
if (callAppRunnerInRunZonedGuarded) {
var runIntegrationsAndAppRunner = () async {
final integrations = options.integrations
.where((i) => i is! RunZonedGuardedIntegration);
await _callIntegrations(integrations, options);
await appRunner();
};

final runZonedGuardedIntegration =
RunZonedGuardedIntegration(runIntegrationsAndAppRunner);
options.addIntegrationByIndex(0, runZonedGuardedIntegration);

// RunZonedGuardedIntegration will run other integrations and appRunner
// runZonedGuarded so all exception caught in the error handler are
// handled
await runZonedGuardedIntegration(HubAdapter(), options);
} else {
await _callIntegrations(options.integrations, options);
await appRunner();
};

final runZonedGuardedIntegration =
RunZonedGuardedIntegration(runIntegrationsAndAppRunner);
options.addIntegrationByIndex(0, runZonedGuardedIntegration);

// RunZonedGuardedIntegration will run other integrations and appRunner
// runZonedGuarded so all exception caught in the error handler are
// handled
await runZonedGuardedIntegration(HubAdapter(), options);
}
} else {
await _callIntegrations(options.integrations, options);
}
Expand Down
25 changes: 25 additions & 0 deletions dart/test/sentry_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,31 @@ void main() {
});
});

test('should complete when appRunner is not called in runZonedGuarded',
() async {
final completer = Completer();
var completed = false;

final init = Sentry.init(
(options) {
options.dsn = fakeDsn;
},
appRunner: () => completer.future,
callAppRunnerInRunZonedGuarded: false,
).whenComplete(() => completed = true);

await Future(() {
// We make the expectation only after all microtasks have completed,
// that Sentry.init might have scheduled.
expect(completed, false);
});

completer.complete();
await init;

expect(completed, true);
});

test('options.environment debug', () async {
final sentryOptions = SentryOptions(dsn: fakeDsn)
..platformChecker = FakePlatformChecker.debugMode();
Expand Down
14 changes: 0 additions & 14 deletions flutter/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -187,20 +187,6 @@ class MainScaffold extends StatelessWidget {
},
child: const Text('Capture from FlutterError.onError'),
),
ElevatedButton(
onPressed: () {
// Only usable on Flutter >= 3.3
// and needs the following additional setup:
// options.addIntegration(OnErrorIntegration());
(WidgetsBinding.instance.platformDispatcher as dynamic)
.onError
?.call(
Exception('PlatformDispatcher.onError'),
StackTrace.current,
);
},
child: const Text('Capture from PlatformDispatcher.onError'),
),
ElevatedButton(
onPressed: () => makeWebRequest(context),
child: const Text('Dart: Web request'),
Expand Down
2 changes: 1 addition & 1 deletion flutter/example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ dependencies:

dev_dependencies:
pedantic: ^1.11.1
sentry_dart_plugin: ^1.0.0-alpha.4
sentry_dart_plugin: ^1.0.0-beta.1

dependency_overrides:
sentry:
Expand Down
4 changes: 0 additions & 4 deletions flutter/lib/src/integrations/on_error_integration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,6 @@ class OnErrorIntegration implements Integration<SentryFlutterOptions> {
// WidgetsBinding works with WidgetsFlutterBinding and other custom bindings
final wrapper = dispatchWrapper ??
PlatformDispatcherWrapper(binding.platformDispatcher);

if (!wrapper.isOnErrorSupported(options)) {
return;
}
_defaultOnError = wrapper.onError;

_integrationOnError = (Object exception, StackTrace stackTrace) {
Expand Down
18 changes: 15 additions & 3 deletions flutter/lib/src/sentry_flutter.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import 'dart:async';
import 'dart:ui';

import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:meta/meta.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:sentry/sentry.dart';
import '../sentry_flutter.dart';
import 'event_processor/android_platform_exception_event_processor.dart';
import 'native_scope_observer.dart';
import 'sentry_native.dart';
Expand All @@ -13,9 +14,7 @@ import 'sentry_native_channel.dart';
import 'event_processor/flutter_enricher_event_processor.dart';
import 'integrations/debug_print_integration.dart';
import 'integrations/native_app_start_integration.dart';
import 'sentry_flutter_options.dart';

import 'default_integrations.dart';
import 'file_system_transport.dart';

import 'version.dart';
Expand Down Expand Up @@ -45,12 +44,17 @@ mixin SentryFlutter {
final native = SentryNative();
native.setNativeChannel(nativeChannel);

final platformDispatcher = PlatformDispatcher.instance;
final wrapper = PlatformDispatcherWrapper(platformDispatcher);
final isOnErrorSupported = wrapper.isOnErrorSupported(flutterOptions);

// first step is to install the native integration and set default values,
// so we are able to capture future errors.
final defaultIntegrations = _createDefaultIntegrations(
packageLoader,
channel,
flutterOptions,
isOnErrorSupported,
);
for (final defaultIntegration in defaultIntegrations) {
flutterOptions.addIntegration(defaultIntegration);
Expand All @@ -65,6 +69,8 @@ mixin SentryFlutter {
appRunner: appRunner,
// ignore: invalid_use_of_internal_member
options: flutterOptions,
// ignore: invalid_use_of_internal_member
callAppRunnerInRunZonedGuarded: !isOnErrorSupported,
);
}

Expand Down Expand Up @@ -96,6 +102,7 @@ mixin SentryFlutter {
PackageLoader packageLoader,
MethodChannel channel,
SentryFlutterOptions options,
bool isOnErrorSupported,
) {
final integrations = <Integration>[];
final platformChecker = options.platformChecker;
Expand All @@ -104,6 +111,11 @@ mixin SentryFlutter {
// Will call WidgetsFlutterBinding.ensureInitialized() before all other integrations.
integrations.add(WidgetsFlutterBindingIntegration());

// Use PlatformDispatcher.onError instead of zones.
if (isOnErrorSupported) {
integrations.add(OnErrorIntegration());
}

// Will catch any errors that may occur in the Flutter framework itself.
integrations.add(FlutterErrorIntegration());

Expand Down
2 changes: 1 addition & 1 deletion flutter/test/mocks.mocks.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Mocks generated by Mockito 5.3.1 from annotations
// Mocks generated by Mockito 5.3.2 from annotations
// in sentry_flutter/example/windows/flutter/ephemeral/.plugin_symlinks/sentry_flutter/example/linux/flutter/ephemeral/.plugin_symlinks/sentry_flutter/example/ios/.symlinks/plugins/sentry_flutter/test/mocks.dart.
// Do not manually edit this file.

Expand Down
Loading

0 comments on commit 02419b7

Please sign in to comment.