Last active
April 26, 2021 12:03
-
-
Save slightfoot/094657bb22e986bbb4c9bafd9841cbd8 to your computer and use it in GitHub Desktop.
Crash Reporting / Error Capture for Flutter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// | |
/// 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()); | |
} | |
} |
runCapturedApp
basically replaces runApp
that you have in main.dart. So it shouldn't be a problem to add to the existing app.
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>
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
I do not understand how can I integrate this with the existing app, can you please throw some light on it?