Skip to content

Instantly share code, notes, and snippets.

@slightfoot
Last active April 26, 2021 12:03
Show Gist options
  • Star 22 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save slightfoot/094657bb22e986bbb4c9bafd9841cbd8 to your computer and use it in GitHub Desktop.
Save slightfoot/094657bb22e986bbb4c9bafd9841cbd8 to your computer and use it in GitHub Desktop.
Crash Reporting / Error Capture for Flutter
///
/// Flutter Captured Error Reporting
/// Created by Simon Lightfoot
///
/// Copyright (C) DevAngels Limited 2018
/// License: APACHE 2.0 - https://www.apache.org/licenses/LICENSE-2.0
///
import 'dart:async';
import 'dart:io';
import 'dart:ui' as ui show window;
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:package_info/package_info.dart';
import 'package:device_info/device_info.dart';
import 'package:system_info/system_info.dart';
import 'package:connectivity/connectivity.dart';
import 'package:sentry/sentry.dart';
const int MEGABYTE = 1024 * 1024;
typedef Future<Null> ErrorReporter(Object error, StackTrace stackTrace);
typedef Widget ErrorReportingWidget(ErrorReporter reporter);
bool get _isInDebugMode {
bool inDebugMode = false;
assert(inDebugMode = true);
return inDebugMode;
}
///
/// Example:
/// void main() => runCapturedApp((reporter) => new AppComponent(reporter), dsn: '<YOUR-DSN>');
///
/// During your app lifecycle you can use the Reporter parameter to report a exception, like this:
/// try {
/// throw new AssertionError();
/// } catch (exception, stackTrace) {
/// errorReporter(exception, stackTrace);
/// }
///
runCapturedApp(ErrorReportingWidget app, {@required String dsn}) {
final SentryClient sentry = SentryClient(dsn: dsn);
FlutterError.onError = (FlutterErrorDetails details) {
if (_isInDebugMode) {
// In development mode simply print to console.
FlutterError.dumpErrorToConsole(details);
} else {
// In production mode report to the application zone to report to Sentry.
Zone.current.handleUncaughtError(details.exception, details.stack);
}
};
runZoned(() {
runApp(app((error, stackTrace) => reportError(sentry, error, stackTrace)));
}, onError: (Object error, StackTrace stackTrace) async {
await reportError(sentry, error, stackTrace);
});
}
Future<Null> reportError(SentryClient sentry, Object error, StackTrace stackTrace) async {
print('Caught error: $error');
if (_isInDebugMode) {
print(stackTrace);
print('In dev mode. Not sending report to Sentry.io.');
return;
}
print('Reporting to Sentry.io...');
final PackageInfo info = await PackageInfo.fromPlatform();
Map<String, dynamic> extra = {};
if (defaultTargetPlatform == TargetPlatform.android) {
extra['device_info'] = await DeviceInfoPlugin.channel.invokeMethod('getAndroidDeviceInfo');
}
else if (defaultTargetPlatform == TargetPlatform.iOS) {
extra['device_info'] = await DeviceInfoPlugin.channel.invokeMethod('getIosDeviceInfo');
}
String mode = _isInDebugMode ? 'checked' : 'release';
Map<String, String> tags = {};
tags['platform'] = defaultTargetPlatform.toString().substring('TargetPlatform.'.length);
tags['package_name'] = info.packageName;
tags['build_number'] = info.buildNumber;
tags['version'] = info.version;
tags['mode'] = mode;
tags['locale'] = ui.window.locale.toString();
ConnectivityResult connectivity = await (Connectivity().checkConnectivity());
tags['connectivity'] = connectivity.toString().substring('ConnectivityResult.'.length);
Map<String, dynamic> uiValues = {};
uiValues['locale'] = ui.window.locale.toString();
uiValues['pixel_ratio'] = ui.window.devicePixelRatio;
uiValues['default_route'] = ui.window.defaultRouteName;
uiValues['physical_size'] = [ui.window.physicalSize.width, ui.window.physicalSize.height];
uiValues['text_scale_factor'] = ui.window.textScaleFactor;
uiValues['view_insets'] = [ui.window.viewInsets.left, ui.window.viewInsets.top, ui.window.viewInsets.right, ui.window.viewInsets.bottom];
uiValues['padding'] = [ui.window.padding.left, ui.window.padding.top, ui.window.padding.right, ui.window.padding.bottom];
if (WidgetsBinding.instance != null) {
// Removed the widget tree as it posts too much information.
/*
if (WidgetsBinding.instance.renderViewElement != null) {
uiValues['render_view'] = WidgetsBinding.instance.renderViewElement.toStringDeep();
} else {
uiValues['render_view'] = '<no tree currently mounted>';
}
*/
}
extra['ui'] = uiValues;
Map<String, dynamic> memory = {};
memory['phys_total'] = '${SysInfo.getTotalPhysicalMemory() ~/ MEGABYTE}MB';
memory['phys_free'] = '${SysInfo.getFreePhysicalMemory() ~/ MEGABYTE}MB';
memory['virt_total'] = '${SysInfo.getTotalVirtualMemory() ~/ MEGABYTE}MB';
memory['virt_free'] = '${SysInfo.getFreeVirtualMemory() ~/ MEGABYTE}MB';
extra['memory'] = memory;
extra['dart_version'] = Platform.version;
final Event event = Event(
loggerName: '',
exception: error,
stackTrace: stackTrace,
release: '${info.version}_${info.buildNumber}',
environment: 'qa',
tags: tags,
extra: extra,
);
try {
final SentryResponse response = await sentry.capture(event: event);
if (response.isSuccessful) {
print('Success! Event ID: ${response.eventId}');
} else {
print('Failed to report to Sentry.io: ${response.error}');
}
}
catch (e, stackTrace) {
print('Exception whilst reporting to Sentry.io\n' + stackTrace.toString());
}
}
@devashish-patel
Copy link

void main() => runCapturedApp((reporter) => new AppComponent(reporter), dsn: '');

I do not understand how can I integrate this with the existing app, can you please throw some light on it?

@itsJoKr
Copy link

itsJoKr commented Dec 12, 2019

@devashish-patel

runCapturedApp basically replaces runApp that you have in main.dart. So it shouldn't be a problem to add to the existing app.

@KiraDiShira
Copy link

When I execute this line of code:

final SentryResponse response = await sentry.capture(event: event);

I got the following exception:

_InternalLinkedHashMap<dynamic, dynamic>' is not a subtype of type 'Map<String, dynamic>

@lironhl
Copy link

lironhl commented Jul 18, 2020

When I execute this line of code:

final SentryResponse response = await sentry.capture(event: event);

I got the following exception:

_InternalLinkedHashMap<dynamic, dynamic>' is not a subtype of type 'Map<String, dynamic>

It's caused by the deviceInfo part of the code.
I solved it naively but the solution definitely works for me, My Fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment