Table of Contents
- 1. Flutter's Error Hierarchy: 3 Surfaces You Must Cover
- 2. FlutterError.onError — Catching Framework Errors
- 3. PlatformDispatcher.instance.onError — Catching Async Errors
- 4. runZonedGuarded — The Zone-Based Safety Net
- 5. try/catch Best Practices in Dart
- 6. Connecting Error Handling to Logtrics Monitoring
- 7. Error Grouping & Deduplication
- 8. Production Error Handling Best Practices
- 9. FAQ
1. Flutter's Error Hierarchy: 3 Surfaces You Must Cover
Most Flutter apps only handle one or two of these error surfaces, leaving silent crashes in production:
Surface 1: Flutter Framework Errors
Widget build errors, layout exceptions, rendering failures. Reported via FlutterError.onError.
Example: accessing a null property in a build() method, invalid constraints in a layout
Surface 2: Async / Platform Errors
Uncaught async errors outside the Flutter widget tree. Captured via PlatformDispatcher.instance.onError.
Example: network call that throws inside a Future without a .catchError(), async isolate errors
Surface 3: Dart Zone Errors
Any uncaught synchronous or async error in the current Dart Zone. Captured with runZonedGuarded.
Example: errors in code paths not covered by the Flutter widget tree or isolate handlers
Important: You need all three to catch 100% of production errors. Missing even one surface means silent crashes that never appear in your crash reporter.
2. FlutterError.onError — Catching Framework Errors
FlutterError.onError is a static callback invoked whenever the Flutter framework detects an error. By default, it just prints to the console. Override it to report to Logtrics:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Logtrics.initialize(apiKey: 'your-api-key');
// Store the original Flutter error handler
final originalOnError = FlutterError.onError;
FlutterError.onError = (FlutterErrorDetails details) {
// Report to Logtrics
Logtrics.reportError(
details.exception,
details.stack,
context: details.context?.toDescription(),
);
// In debug mode, also show the red screen
if (kDebugMode) {
originalOnError?.call(details);
}
};
runApp(const MyApp());
}
Best practice: Always call the original handler in debug mode to preserve the red screen experience for local development. In production, silence it to avoid unwanted log spam — Logtrics has it.
3. PlatformDispatcher.instance.onError — Catching Async Errors
Introduced in Flutter 3.3, PlatformDispatcher.instance.onError catches errors from async code that runs outside the Flutter widget tree, including Future chains and isolates:
PlatformDispatcher.instance.onError = (Object error, StackTrace stack) {
Logtrics.reportError(error, stack,
context: 'PlatformDispatcher',
);
return true; // CRITICAL: return true to suppress default crash
};
Critical: Always return true from this handler. Returning false causes Flutter to crash the app after the handler runs, which is usually not what you want in production.
4. runZonedGuarded — The Zone-Based Safety Net
runZonedGuarded wraps your entire app in a Dart Zone with a custom error handler — the ultimate catch-all for any error that escapes both FlutterError and PlatformDispatcher:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:logtrics/logtrics.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Logtrics.initialize(apiKey: 'your-api-key');
// Surface 1: Flutter framework errors
FlutterError.onError = (details) =>
Logtrics.reportError(details.exception, details.stack);
// Surface 2: Async / platform errors (Flutter 3.3+)
PlatformDispatcher.instance.onError = (error, stack) {
Logtrics.reportError(error, stack);
return true;
};
// Surface 3: Zone-level catch-all
runZonedGuarded(
() => runApp(const MyApp()),
(error, stack) {
Logtrics.reportError(error, stack,
context: 'runZonedGuarded',
);
},
);
}
This pattern gives you 100% error coverage across all three surfaces. With Logtrics initialized first, every captured error is transmitted to the dashboard with full session context.
5. try/catch Best Practices in Dart
Beyond the global handlers, follow these Dart try/catch patterns for cleaner error handling in feature code:
// ❌ Too broad — hides bugs, catches programming errors
try {
await fetchPaymentStatus();
} catch (e) {
showError('Something went wrong');
}
// ✅ Specific — handle known failures, let bugs propagate
try {
await fetchPaymentStatus();
} on NetworkException catch (e, stack) {
Logtrics.warn('Payment status fetch failed', error: e);
showRetryDialog();
} on AuthException catch (e, stack) {
Logtrics.error('Auth expired during checkout', error: e, stackTrace: stack);
navigateToLogin();
}
Always pass the StackTrace to your logger
The second parameter in catch (e, stack) is the stack trace. Always pass it to Logtrics.error(error: e, stackTrace: stack) — without it, the crash reporter can't show you where the error originated.
6. Connecting Error Handling to Logtrics Monitoring
Once all three error surfaces feed into Logtrics, you get a unified view of every exception across your entire user base:
In the Logtrics Dashboard
- ✓ Group errors by type (framework / async / zone)
- ✓ Filter by app version to spot regressions
- ✓ See impacted user count per error group
- ✓ AI root cause summary per crash group
- ✓ Session logs before each crash
Enriching Error Reports
Logtrics.addBreadcrumb(
category: 'navigation',
message: 'Entered CheckoutScreen',
);
Logtrics.setExtra(
'cart_total', cartTotal,
);
7. Error Grouping & Deduplication
The same Flutter bug can produce slightly different stack traces depending on the device model, OS version, and Dart runtime version. Without smart grouping, you'd see hundreds of "different" errors that are really the same bug.
Logtrics uses AI to group errors by semantic root cause rather than exact stack trace match. This means:
Without smart grouping
312 separate "crash reports" for what is actually 1 bug in your payment flow, split across device models and OS versions
With Logtrics AI grouping
1 crash group "NullPointerException in CheckoutScreen.build" affecting 312 users — fix once, resolve all
8. Production Error Handling Best Practices
Initialize Logtrics before WidgetsFlutterBinding
Call await Logtrics.initialize() before WidgetsFlutterBinding.ensureInitialized() to ensure errors during Flutter initialization are captured.
Don't swallow errors silently
A bare catch (e) { } is one of the most dangerous patterns in Dart. Always log at minimum a warning to Logtrics, even for expected error paths. Silent errors hide bugs that compound into major production incidents.
Use custom Flutter ErrorWidget in release mode
Override ErrorWidget.builder to show a user-friendly error screen instead of the red "RenderFlex" screen. Users see a graceful fallback; Logtrics still receives the full error details.
Track error rates by Flutter version
Tag every error report with the Flutter SDK version and app build number. When you upgrade Flutter, filter Logtrics by app version to immediately see if the upgrade introduced regressions before it reaches all users.
9. FAQ
How do I catch all exceptions in a Flutter app?
Use all three handlers: FlutterError.onError for widget errors, PlatformDispatcher.instance.onError for async errors, and runZonedGuarded as a catch-all Zone. Logtrics wires all three automatically when you set captureAllErrors: true in the config.
What is FlutterError.onError used for?
It intercepts errors reported by the Flutter framework — widget build errors, rendering exceptions, and framework assertions. Override it to forward these to your crash reporter instead of just printing to the console in production.
What is runZonedGuarded in Flutter?
It runs code inside a Dart Zone — a sandboxed execution context with its own error handler. Any uncaught error that propagates to the Zone boundary is passed to the onError callback, making it a catch-all safety net for your app.
Monitor Every Flutter Exception in Production
Don't let silent exceptions erode your app's quality. Logtrics captures every Flutter error across all three surfaces and gives you AI-powered root cause analysis.