crash_reporting (1.0.0)
Installation
dart pub add crash_reporting:1.0.0 --hosted-url=About this package
Dart.Package.CrashReporting
Crash reporting for Flutter apps, published as crash_reporting on the Platform pub feed. Reports go to the app's own backend as JSON; there is no third-party service.
What it catches:
- Dart errors: framework errors (
FlutterError.onError), unhandled async errors (PlatformDispatcher.onError) and whatever the app'srunZonedGuardedsees. An image that fails to load for a network reason is a log warning, not a report. - Android process deaths Dart never sees (native crash, GPU driver, WebView, ANR, out of memory, Java crash), reported on the next launch from
ActivityManager.getHistoricalProcessExitReasons(API 30+). The first check looks back a week; later checks start where the last one stopped.- A native tombstone is decoded from protobuf into debuggerd's text form: the crashing thread's backtrace first (rel pc, library, BuildId), then the logcat lines Android kept, then six frames of every other thread, so the 60,000-character cap cuts the least useful part.
- Android keeps no trace for a Java crash, so the plugin writes the uncaught exception to
files/last_java_crash.txtand attaches it to an exit within a minute of it. - A crash on the WebView's in-process GPU thread (
Chrome_InProcGpin a GPU driver library) is flaggedwebViewGpuCrash: truein the exit, so an app can turn its WebView feature off on the next launch.
Each report carries the device, OS, screen, route, lifecycle, memory, build mode, a settings snapshot without anything named like a token or secret, and the last 400 log lines. Emails and token-like strings are scrubbed; BuildId: and build_id: hex is kept because symbolizing needs it. Reports are on by default; the user's choice and a random install id (a dashed Guid) are kept in a store the app provides.
Wiring it into an app
Future<void> main() async {
late final CrashReporter crashReporter;
runZonedGuarded(
() async {
WidgetsFlutterBinding.ensureInitialized();
final store = await openMyKeyValueStore('crash_reporting.json');
crashReporter = CrashReporter(
api: HttpCrashReportApi(client: http.Client(), baseUrl: Uri.parse(apiBaseUrl)),
store: (read: store.read, write: store.write),
appVersion: appVersion,
platform: platformName,
facts: CrashContext.facts(appVersion: appVersion, earlierLog: readLogFile),
);
await crashReporter.initialize();
unawaited(CrashContext.loadDevice().then((_) => crashReporter.reportNativeExits()));
reportUncaughtErrors(crashReporter, logError: log.error, logWarning: log.warning);
runApp(const MyApp());
},
(error, stack) => crashReporter.reportError(error, stack, kind: 'zone'),
);
}
Feed the context as the app runs:
CrashContext.addLine(line)from the app's logger, already scrubbed of anything app-specific.CrashContext.routefrom aNavigatorObserver,CrashContext.lifecyclefrom aWidgetsBindingObserver.CrashContext.useSettings(settings.toJson())whenever settings change.
crashReporter.setEnabled(bool) backs the user's switch in settings.
The backend contract
POST {baseUrl}/errors (HttpCrashReportApi.path to change it), anonymous, application/json:
| Field | |
|---|---|
message, stackTrace |
Scrubbed. A native exit's stackTrace is the decoded tombstone or Java trace |
appVersion, platform, installId |
installId is a dashed Guid |
occurredAt |
UTC ISO 8601; for a native exit, when the process died |
kind |
flutter, unhandled, zone, or an Android exit reason: crash-native, crash-java, anr, init-failure, excessive-resources, low-memory, signaled |
context |
Optional JSON string: the device facts above, plus exit and reportedLater for a native exit |
log |
Optional: the last 60,000 characters of the log, cut from the front |
Any status of 400 or above counts as a failure; failures are swallowed so reporting never throws into the app. Service.Pedalsim.Backend's ErrorsController is a working receiver.
Testing
flutter test runs the Dart tests. The Kotlin tests (android/src/test) need an Android SDK, which CI lacks; run them from any app that depends on the package:
android/gradlew -p android :crash_reporting:testDebugUnitTest
Publishing
Bump Version.txt and push to main; flutter-publish publishes it when that version is not on the feed yet.