diff --git a/rollbar_flutter/lib/src/flutter_error.dart b/rollbar_flutter/lib/src/hooks/flutter_hook.dart similarity index 51% rename from rollbar_flutter/lib/src/flutter_error.dart rename to rollbar_flutter/lib/src/hooks/flutter_hook.dart index bb32cb5..0358f6e 100644 --- a/rollbar_flutter/lib/src/flutter_error.dart +++ b/rollbar_flutter/lib/src/hooks/flutter_hook.dart @@ -1,15 +1,17 @@ import 'package:flutter/foundation.dart'; +import 'package:meta/meta.dart'; import 'package:rollbar_common/rollbar_common.dart'; import 'package:rollbar_dart/rollbar.dart'; -import 'extension/diagnostics.dart'; +import '../extension/diagnostics.dart'; +import 'hook.dart'; -extension RollbarFlutterError on FlutterError { - /// Called whenever the Flutter framework catches an error. - /// - /// The default behavior is to call [presentError]. - static void onError(FlutterErrorDetails error) { +@sealed +class FlutterHook implements Hook { + FlutterExceptionHandler? _originalOnError; + + void onError(FlutterErrorDetails error) { if (!error.silent) { Rollbar.drop( Breadcrumb.error( @@ -27,6 +29,22 @@ extension RollbarFlutterError on FlutterError { Rollbar.error(error.exception, error.stack ?? StackTrace.empty); } - FlutterError.presentError(error); + if (_originalOnError != null) { + _originalOnError!(error); + } + } + + @override + void install(_) { + _originalOnError = FlutterError.onError; + FlutterError.onError = onError; + } + + @override + void uninstall() { + if (FlutterError.onError == onError) { + FlutterError.onError = _originalOnError; + _originalOnError = null; + } } } diff --git a/rollbar_flutter/lib/src/hooks/hook.dart b/rollbar_flutter/lib/src/hooks/hook.dart new file mode 100644 index 0000000..0e84eb6 --- /dev/null +++ b/rollbar_flutter/lib/src/hooks/hook.dart @@ -0,0 +1,9 @@ +import 'dart:async'; + +import 'package:rollbar_dart/rollbar.dart'; + +abstract class Hook { + FutureOr install(final Config config); + + FutureOr uninstall(); +} diff --git a/rollbar_flutter/lib/src/hooks/platform_hook.dart b/rollbar_flutter/lib/src/hooks/platform_hook.dart new file mode 100644 index 0000000..09632eb --- /dev/null +++ b/rollbar_flutter/lib/src/hooks/platform_hook.dart @@ -0,0 +1,34 @@ +import 'dart:ui'; +import 'package:rollbar_dart/rollbar.dart'; +import 'hook.dart'; + +class PlatformHook implements Hook { + ErrorCallback? _originalOnError; + PlatformDispatcher? _platformDispatcher; + + bool onError(Object exception, StackTrace stackTrace) { + Rollbar.error(exception, stackTrace); + + if (_originalOnError != null) { + return _originalOnError!(exception, stackTrace); + } + + return false; + } + + @override + void install(_) { + _platformDispatcher = PlatformDispatcher.instance; + _originalOnError = _platformDispatcher?.onError; + _platformDispatcher?.onError = onError; + } + + @override + void uninstall() { + if (_platformDispatcher?.onError == onError) { + _platformDispatcher?.onError = _originalOnError; + _originalOnError = null; + _platformDispatcher = null; + } + } +} diff --git a/rollbar_flutter/lib/src/rollbar.dart b/rollbar_flutter/lib/src/rollbar.dart index 76e83c6..625e201 100644 --- a/rollbar_flutter/lib/src/rollbar.dart +++ b/rollbar_flutter/lib/src/rollbar.dart @@ -7,7 +7,9 @@ import 'package:meta/meta.dart'; import 'package:rollbar_dart/rollbar.dart'; -import 'flutter_error.dart'; +import 'hooks/hook.dart'; +import 'hooks/flutter_hook.dart'; +import 'hooks/platform_hook.dart'; import 'platform_transformer.dart'; extension _Methods on MethodChannel { @@ -22,48 +24,54 @@ extension _Methods on MethodChannel { typedef RollbarClosure = FutureOr Function(); @sealed +@immutable class RollbarFlutter { static const _platform = MethodChannel('com.rollbar.flutter'); - RollbarFlutter._(); + const RollbarFlutter._(); static Future run( Config config, RollbarClosure appRunner, ) async { if (!config.handleUncaughtErrors) { - WidgetsFlutterBinding.ensureInitialized(); - - await _run(config, appRunner, null); - - return; + await _run(config, appRunner); + } else if (requiresCustomZone) { + await runZonedGuarded( + () async => await _run(config, appRunner, [FlutterHook()]), + Rollbar.error); + } else { + await _run(config, appRunner, [FlutterHook(), PlatformHook()]); } - - await runZonedGuarded(() async { - WidgetsFlutterBinding.ensureInitialized(); - - await _run(config, appRunner, RollbarFlutterError.onError); - }, (exception, stackTrace) { - Rollbar.error(exception, stackTrace); - }); } static Future _run( Config config, - RollbarClosure appRunner, - FlutterExceptionHandler? onError, - ) async { + RollbarClosure appRunner, [ + List hooks = const [], + ]) async { + WidgetsFlutterBinding.ensureInitialized(); + await Rollbar.run(config.copyWith( framework: 'flutter', persistencePath: await _platform.persistencePath, transformer: (_) => PlatformTransformer(), )); - if (onError != null) { - FlutterError.onError = onError; + for (final hook in hooks) { + await hook.install(config); } await _platform.initialize(config: config); await appRunner(); } + + static bool get requiresCustomZone { + try { + (PlatformDispatcher.instance as dynamic)?.onError; + return false; + } on NoSuchMethodError { + return true; + } + } }