exception_catcher 0.1.1
exception_catcher: ^0.1.1 copied to clipboard
Flutter exception collection and reporter.
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.