exception_catcher
A Flutter exception collection and reporting facade.
This package focuses on capturing, normalizing, filtering, deduplicating, and dispatching exceptions. It does not bind to any specific reporting platform. You can connect Bugly, Firebase Crashlytics, or a custom reporting service by implementing ExceptionHandler.
Features
- Captures Flutter framework errors from
FlutterError.onError - Captures platform dispatcher errors from
PlatformDispatcher.instance.onError - Captures Dart zone async errors through
runAppGuarded - Supports manual exception recording with
recordError - Supports app context with
contextProvider - Supports per-report context with
customContext - Supports breadcrumbs with
Breadcrumb - Supports exception filtering with
filter - Supports fingerprint-based deduplication with
ExceptionDeduplicator - Supports dispatching reports to multiple
ExceptionHandlerinstances
Project structure
lib/
├── exception_catcher.dart # Public export entry
└── src/
├── exception_catcher.dart # Core facade for initialization, capture, and dispatch
├── config/
│ └── exception_catcher_config.dart # Configuration definitions
├── deduplication/
│ └── exception_deduplicator.dart # Exception fingerprint deduplication
├── handler/
│ ├── exception_handler.dart # Reporting handler interface
│ ├── console_exception_handler.dart # Console output handler
│ └── memory_exception_handler.dart # In-memory handler for tests
├── mode/
│ ├── report_mode.dart # Report mode interface
│ └── silent_report_mode.dart # Silent report mode
└── model/
├── breadcrumb.dart # Breadcrumb model
├── exception_report.dart # Standard exception report model
└── exception_types.dart # Exception source and severity enums
Core concepts
ExceptionCatcher
ExceptionCatcher is the main entry point.
It is responsible for:
- Installing
FlutterError.onError - Installing
PlatformDispatcher.instance.onError - Capturing async errors with
runZonedGuarded - Creating standardized
ExceptionReportobjects - Running filtering, deduplication, and report mode checks
- Dispatching reports to multiple
ExceptionHandlerinstances - Keeping breadcrumbs before an exception occurs
Common methods:
| Method | Description |
|---|---|
initialize() |
Initializes configuration and installs global error hooks |
runAppGuarded() |
Starts the app inside a guarded zone |
recordError() |
Records an exception manually |
addBreadcrumb() |
Adds a breadcrumb |
clearBreadcrumbs() |
Clears breadcrumbs |
dispose() |
Disposes handlers and resets runtime state |
ExceptionCatcherConfig
ExceptionCatcherConfig controls capture and dispatch behavior.
It configures:
- Which
ExceptionHandlerinstances to use - Whether to capture Flutter framework errors
- Whether to capture PlatformDispatcher errors
- Whether to forward errors to existing handlers
- Whether to filter reports
- Whether to deduplicate reports
- Handler timeout
- Maximum breadcrumb count
- App context provider
contextProvider
ExceptionReport
ExceptionReport is the standard data model generated for every exception.
It contains:
- Exception message
message - Exception type
errorType - Stack trace
stackTrace - Source
source - Severity
severity - Flutter error context
- App context
appContext - Custom context
customContext - Breadcrumbs
breadcrumbs
Use toJson() to create uploadable data. Use redact() to redact sensitive fields.
ExceptionHandler
ExceptionHandler is the integration point for reporting platforms.
Built-in handlers:
ConsoleExceptionHandler: prints reports during developmentMemoryExceptionHandler: stores reports in memory for tests
In production, implement a custom handler and upload ExceptionReport to Bugly, Firebase Crashlytics, or your own service.
ExceptionDeduplicator
ExceptionDeduplicator skips repeated reports by exception fingerprint and time window.
The fingerprint is generated from exception type, message, and the first stack trace lines.
The default deduplication window is 1 minute.
ReportMode
ReportMode controls whether a report should continue to be handled.
The default SilentReportMode allows reports without showing UI or blocking app flow.
Installation
Add the dependency:
dependencies:
exception_catcher: ^0.1.0
Then run:
flutter pub get
Quick start
Use ExceptionCatcher.runAppGuarded in your app entry point.
await ExceptionCatcher.runAppGuarded(
() async {
WidgetsFlutterBinding.ensureInitialized();
await preInit();
runApp(const App());
},
config: ExceptionCatcherConfig(
handlers: <ExceptionHandler>[
ConsoleExceptionHandler(includeStackTrace: true),
],
contextProvider: () async => <String, Object?>{
'env': 'dev',
'appVersion': '1.0.0',
'buildNumber': '1',
},
),
);
App integration
Recommended startup order:
- Call
ExceptionCatcher.runAppGuarded()frommain() - Call
WidgetsFlutterBinding.ensureInitialized()inside its callback - Initialize app dependencies inside the same callback
- Call
runApp()inside the same callback
runAppGuarded uses runZonedGuarded internally.
Keep WidgetsFlutterBinding.ensureInitialized() and runApp() in the same callback to avoid Flutter zone mismatch errors.
Avoid setting these again manually:
FlutterError.onErrorPlatformDispatcher.instance.onError- Outer
runZonedGuarded
They are handled by ExceptionCatcher.
Manual recording
Callers can record exceptions manually:
try {
await doSomething();
} catch (error, stackTrace) {
await ExceptionCatcher.recordError(
error,
stackTrace,
source: ExceptionSource.manual,
customContext: <String, Object?>{
'module': 'checkout',
'action': 'submit',
},
);
rethrow;
}
Breadcrumbs
Record important actions before an exception occurs:
ExceptionCatcher.addBreadcrumb(
'open settings page',
category: 'navigation',
data: <String, Object?>{
'from': 'home',
},
);
Clear breadcrumbs:
ExceptionCatcher.clearBreadcrumbs();
Custom handler
Extend ExceptionHandler to integrate a reporting platform:
class CustomExceptionHandler extends ExceptionHandler {
@override
String get name => 'custom';
@override
Future<void> initialize() async {
// Initialize SDK.
}
@override
Future<HandlerResult> handle(ExceptionReport report) async {
final payload = report.redact(<String>{
'password',
'token',
'code',
}).toJson();
await upload(payload);
return const HandlerResult.handled();
}
Future<void> upload(Map<String, Object?> payload) async {}
}
Configuration
| Field | Description | Default |
|---|---|---|
handlers |
Exception handlers | ConsoleExceptionHandler() |
reportMode |
Report mode | SilentReportMode() |
filter |
Exception filter | null |
deduplicator |
Exception deduplicator | ExceptionDeduplicator() |
contextProvider |
App context provider | null |
onHandlerError |
Handler failure callback | null |
handlerTimeout |
Single handler timeout | 10s |
maxBreadcrumbs |
Maximum breadcrumb count | 50 |
handleFlutterErrors |
Capture Flutter framework errors | true |
handlePlatformDispatcherErrors |
Capture PlatformDispatcher errors | true |
forwardToExistingHandlers |
Forward to existing error handlers | true |
platformDispatcherErrorResult |
PlatformDispatcher error result | true |
Exception sources
| Enum | Description |
|---|---|
ExceptionSource.manual |
Manually recorded exception |
ExceptionSource.flutter |
Flutter framework exception |
ExceptionSource.platformDispatcher |
PlatformDispatcher exception |
ExceptionSource.zone |
Dart zone async exception |
ExceptionSource.network |
Manually recorded network-chain exception |
Built-in handlers
ConsoleExceptionHandler
Prints reports to the console during development.
const ConsoleExceptionHandler(includeStackTrace: true)
MemoryExceptionHandler
Stores reports in memory for tests.
final handler = MemoryExceptionHandler();
Redaction
ExceptionReport.redact() replaces values for matching keys:
final safeReport = report.redact(<String>{
'password',
'token',
'verificationCode',
});
Production handlers should redact sensitive data before uploading reports.
Testing
Run tests:
flutter test
Run analysis:
flutter analyze
Example
The repository includes a minimal Flutter example app:
example/
├── pubspec.yaml
└── lib/
└── main.dart
The example shows how to:
- Initialize and start the app with
runAppGuarded() - Print reports with
ConsoleExceptionHandler - Add app context with
contextProvider - Add breadcrumbs with
addBreadcrumb() - Record handled exceptions with
recordError() - Verify zone capture with an unhandled async exception
Run the example:
cd example
flutter pub get
flutter run
Scope
exception_catcher does not provide:
- Error dialogs or page-level error UI
- Built-in reporting platform SDK bindings
- Local persistent queues
- Offline retry strategy
- Automatic sensitive field detection
Implement these in a custom ExceptionHandler or the application layer.