Skip to content

Commit

Permalink
Use new PlatformDispatcher onError method if available (#109)
Browse files Browse the repository at this point in the history
* Use new PlatformDispatcher onError method if available
* Updated Flutter Demo with more types of possible uncaught errors
  • Loading branch information
matux authored Aug 2, 2023
1 parent daba1fc commit 6198568
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 36 deletions.
31 changes: 22 additions & 9 deletions rollbar_flutter/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ class MyHomePageState extends State<MyHomePage> {

setState(() {
if (++_counter % 2 == 0) {
throw ArgumentError('Unavoidable failure');
throw ArgumentError('Failed to increment counter');
} else {
Rollbar.drop(
rollbar.Breadcrumb.log('Counter incremented to $_counter'),
Expand All @@ -113,6 +113,14 @@ class MyHomePageState extends State<MyHomePage> {
});
}

void asyncFailure() {
_asyncFailure(1).then((n) => log('$n ~/ 0 = ???'));
}

Future<int> _asyncFailure(int num) async {
return Future<int>.delayed(Duration(seconds: num), () => num ~/ 0);
}

void setUser() {
if (_userIsLoggedIn) {
Rollbar.setUser(null);
Expand All @@ -133,10 +141,10 @@ class MyHomePageState extends State<MyHomePage> {
});
}

void divideByZero() {
Rollbar.drop(rollbar.Breadcrumb.log('Tapped divideByZero button'));
Rollbar.critical('About to divide by zero, this won\'t work!');
1 ~/ 0;
void throwError() {
Rollbar.drop(rollbar.Breadcrumb.log('Tapped throwError button'));
Rollbar.critical('About to throw an error!');
throw StateError('A state error occurred');
}

void crash() {
Expand All @@ -157,10 +165,6 @@ class MyHomePageState extends State<MyHomePage> {
child: const Text('Call Faulty Method'),
),
Text(_faultyMsg),
ElevatedButton(
onPressed: divideByZero,
child: const Text('Divide by zero'),
),
if (Platform.isIOS)
ElevatedButton(
onPressed: crash,
Expand All @@ -172,6 +176,15 @@ class MyHomePageState extends State<MyHomePage> {
child: Text(_setUserText),
),
const Divider(),
ElevatedButton(
onPressed: asyncFailure,
child: const Text('Async failure'),
),
ElevatedButton(
onPressed: throwError,
child: const Text('Throw error'),
),
const Divider(),
const Text('Times you have pushed the plus button:'),
Text(
'$_counter',
Expand Down
Original file line number Diff line number Diff line change
@@ -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(
Expand All @@ -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;
}
}
}
9 changes: 9 additions & 0 deletions rollbar_flutter/lib/src/hooks/hook.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import 'dart:async';

import 'package:rollbar_dart/rollbar.dart';

abstract class Hook {
FutureOr<void> install(final Config config);

FutureOr<void> uninstall();
}
34 changes: 34 additions & 0 deletions rollbar_flutter/lib/src/hooks/platform_hook.dart
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
48 changes: 28 additions & 20 deletions rollbar_flutter/lib/src/rollbar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -22,48 +24,54 @@ extension _Methods on MethodChannel {
typedef RollbarClosure = FutureOr<void> Function();

@sealed
@immutable
class RollbarFlutter {
static const _platform = MethodChannel('com.rollbar.flutter');

RollbarFlutter._();
const RollbarFlutter._();

static Future<void> 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<void> _run(
Config config,
RollbarClosure appRunner,
FlutterExceptionHandler? onError,
) async {
RollbarClosure appRunner, [
List<Hook> 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;
}
}
}

0 comments on commit 6198568

Please sign in to comment.