import 'package:collection/collection.dart'; import 'package:firebase_crashlytics/firebase_crashlytics.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:stack_trace/stack_trace.dart'; abstract class ReportService { bool get isCollectionEnabled; Future setCollectionEnabled(bool enabled); Future log(String message); Future setCustomKey(String key, Object value); Future setCustomKeys(Map map); Future recordError(dynamic exception, StackTrace? stack); Future recordFlutterError(FlutterErrorDetails flutterErrorDetails); } class CrashlyticsReportService extends ReportService { FirebaseCrashlytics get instance => FirebaseCrashlytics.instance; @override bool get isCollectionEnabled => instance.isCrashlyticsCollectionEnabled; @override Future setCollectionEnabled(bool enabled) => instance.setCrashlyticsCollectionEnabled(enabled); @override Future log(String message) => instance.log(message); @override Future setCustomKey(String key, Object value) => instance.setCustomKey(key, value); @override Future setCustomKeys(Map map) { final _instance = instance; return Future.forEach>(map.entries, (kv) => _instance.setCustomKey(kv.key, kv.value)); } @override Future recordError(dynamic exception, StackTrace? stack) { if (exception is PlatformException && stack != null) { // simply creating a trace with `Trace.current(1)` or creating a `Trace` from modified frames // does not yield a stack trace that Crashlytics can segment, // so we reconstruct a string stack trace instead stack = StackTrace.fromString(Trace.from(stack) .frames .skip(2) .toList() .mapIndexed( (i, f) => '#${(i++).toString().padRight(8)}${f.member} (${f.uri}:${f.line}:${f.column})', ) .join('\n')); } return instance.recordError(exception, stack); } @override Future recordFlutterError(FlutterErrorDetails flutterErrorDetails) { return instance.recordFlutterError(flutterErrorDetails); } }