appInitErrorHandling function
Future<void>
appInitErrorHandling(
)
Implementation
Future<void> appInitErrorHandling() async {
final config = errorReportingConfig;
// Set the error reporting configuration in Logger
Logger.setErrorReportingConfig(config);
// Check for master kill switch first - this disables ALL error reporting
// regardless of other settings. Use for live Firebase dev projects.
if (AppConfigBase.doDisableErrorReporting) {
debugPrint('Error Reporting DISABLED via DO_DISABLE_ERROR_REPORTING');
_setupMinimalErrorHandlers();
// (b) Kill-switch path — reporting is disabled, so the early buffer cannot
// be flushed anywhere; drop+clear it (a no-op when the installer was never
// called). MUST run before the early `return`, or the buffer leaks (Issue 35).
_logAndClearEarlyErrorBuffer('error reporting disabled (kill switch)');
// Reporting is off — no chokepoint target, so `_recordErrorSafe` (e.g. a
// re-routed isolate error) buffers (and that buffer is dropped next attach).
_activeReporter = null;
// Reporting is off — drop any deferred bootstrap diagnostics and mark
// attached so later ones short-circuit (loge no-ops under the kill switch).
_errorHandlingAttached = true;
_dropDeferredBootstrapReports();
return;
}
// Determine if error reporting is blocked by emulator mode
// Can be overridden with DO_FORCE_ERROR_REPORTING for testing
final isBlockedByEmulator =
AppConfigBase.doUseBackendEmulator && !AppConfigBase.doForceErrorReporting;
// Determine if we should use error reporting
// This must be checked BEFORE initializing reporters to prevent
// Sentry/Crashlytics from capturing errors when running in emulator
final shouldUseErrorReporting = !isBlockedByEmulator &&
(config.enableInDebug || !kDebugMode) &&
(config.enableOnWeb || !kIsWeb);
// Custom reporter follows the same rules as main error reporting
// The enableOnWeb/enableInDebug flags allow reporting on those platforms,
// but only when not blocked by emulator mode or master kill switch
final shouldUseCustomReporter = config.customReporter != null &&
!isBlockedByEmulator &&
((config.enableInDebug || !kDebugMode) && (config.enableOnWeb || !kIsWeb));
debugPrint('Error Reporting Configuration: '
'customReporter=${config.customReporter != null}, '
'customReporterManagesErrorHandlers=${config.customReporterManagesErrorHandlers}, '
'reporterRequiresFirebase=${config.reporterRequiresFirebase}, '
'enableInDebug=${config.enableInDebug}, '
'enableOnWeb=${config.enableOnWeb}, '
'doUseBackendEmulator=${AppConfigBase.doUseBackendEmulator}, '
'doForceErrorReporting=${AppConfigBase.doForceErrorReporting}, '
'isBlockedByEmulator=$isBlockedByEmulator, '
'shouldUseErrorReporting=$shouldUseErrorReporting, '
'shouldUseCustomReporter=$shouldUseCustomReporter, '
'environmentType=${AppConfigBase.environmentType.value}');
// Initialize custom reporter ONLY if it should be used
// This prevents Sentry/etc from setting up internal error handlers
// when running in emulator mode
if (shouldUseCustomReporter) {
await config.customReporter!.initialize();
// Set the custom error reporter in Logger for crash reporting
Logger.setCustomErrorReporter(config.customReporter);
// Attach the chokepoint target so `_recordErrorSafe` (the isolate listener,
// plus the Phase-4 zone/web-JS surfaces) forwards instead of buffering
// (ERH-011/021). Set here so it is live BEFORE the buffer flush below.
_activeReporter = config.customReporter;
} else {
// No active reporter on this path — `_recordErrorSafe` falls back to
// buffering (its buffer is dropped on the disabled/blocked branch below).
_activeReporter = null;
}
// Disable analytics and crashlytics for web or emulator (unless configured otherwise)
if (!shouldUseErrorReporting) {
FlutterError.onError = (details) {
_presentErrorInDebugConsole(details);
loge(details.stack ?? StackTrace.current, details.exceptionAsString());
// Still report to custom reporter if it was initialized (enabled on web/debug)
if (shouldUseCustomReporter) {
config.customReporter!.recordFlutterError(details);
}
};
PlatformDispatcher.instance.onError = (exception, stackTrace) {
loge(stackTrace, exception.toString());
// Still report to custom reporter if it was initialized (enabled on web/debug)
if (shouldUseCustomReporter) {
config.customReporter!.recordError(exception, stackTrace);
}
return true;
};
// (c) Emulator-blocked / debug-no-reporting branch. The minimal handlers
// above replaced the early buffering handlers; reporting is suppressed on
// this path, so drop+clear the early buffer (a no-op when the installer was
// never called). This branch does NOT early-return — the buffer would
// otherwise leak (Issue 35).
_logAndClearEarlyErrorBuffer('error reporting blocked (emulator/debug)');
// Reporting suppressed on this path — drop deferred bootstrap diagnostics
// and mark attached (later ones short-circuit; loge no-ops here).
_errorHandlingAttached = true;
_dropDeferredBootstrapReports();
} else {
// Production branch. dreamic ships no built-in reporter, so the handlers
// route to the single registered reporter (if any).
// Only install handlers if the reporter doesn't manage its own (e.g. Sentry
// via SentryFlutter.init with appRunner). When it does, dreamic leaves
// FlutterError.onError / PlatformDispatcher.onError / the isolate listener to
// the reporter and just wires loge() + flushes the buffers below.
if (!config.customReporterManagesErrorHandlers) {
// Flutter-framework (non-async) errors.
FlutterError.onError = (FlutterErrorDetails details) {
_presentErrorInDebugConsole(details);
if (shouldUseCustomReporter) {
config.customReporter!.recordFlutterError(details);
}
};
// Async / platform errors.
PlatformDispatcher.instance.onError = (error, stack) {
if (shouldUseCustomReporter) {
config.customReporter!.recordError(error, stack);
}
return true;
};
// Errors outside the Flutter framework (isolates), non-web only.
// Apply-once across gate-retry re-runs: `addErrorListener` allows multiple
// listeners, so an unguarded re-run would accumulate them (Issue 31).
//
// Registered AT attach and routed through `_recordErrorSafe` (ERH-021), so
// isolate errors get the same re-entrancy guard, cross-surface dedup, and
// redaction as every other surface (BEH-1/9/11). Pre-attach uncaught main-
// isolate errors are already covered by the early `FlutterError.onError` /
// `PlatformDispatcher.onError` handlers (which buffer) — this listener does
// NOT buffer pre-attach isolate errors (ERH-029).
if (!kIsWeb && !_isolateErrorListenerAdded) {
_isolateErrorListenerAdded = true;
Isolate.current.addErrorListener(
RawReceivePort((pair) {
final List<dynamic> errorAndStacktrace = pair;
final error = errorAndStacktrace.first as Object;
final stackTrace = errorAndStacktrace.last as StackTrace;
_recordErrorSafe(error, stackTrace);
}).sendPort,
);
}
}
// (a) Production branch — the reporter is now attached. Flush the early
// buffers in BREADCRUMBS-then-ERRORS order (ERH-032 / BEH-12) so each
// buffered early error is reported with its preceding breadcrumb trail
// already in place. Flushed breadcrumbs were redacted at emit time and are
// NOT re-redacted. Both flushes are safe no-ops when their buffers are empty
// (e.g. the installer was never called — Issue 36).
if (_activeReporter != null) {
_flushAndClearEarlyBreadcrumbs(_activeReporter!);
}
_flushAndClearEarlyErrorBuffer();
// Reporter attached: deliver any deferred bootstrap diagnostics (e.g. the
// appInitFirebase recovery, which fired at the Firebase step before this
// attach), then route future ones immediately.
_errorHandlingAttached = true;
_flushDeferredBootstrapReports();
}
}