Coding Liquids walkthroughs follow a test crash from the device to a readable console stack.
Separate fatal and non-fatal reports
Crashlytics groups fatal crashes and recorded non-fatal errors so teams can prioritise stability. Use non-fatal recording for caught failures that indicate a defect, not every expected validation response.
Flooding reports with normal user mistakes hides actionable crashes.
Route Flutter framework errors
Assign FlutterError.onError to FirebaseCrashlytics.instance.recordFlutterFatalError after Firebase initialisation. This captures errors delivered through Flutter framework callbacks with their stack traces.
Overwriting the handler later can silently disconnect Crashlytics.
flutter pub add firebase_crashlytics
The Instagram crash pipeline shows where framework, asynchronous, isolate, and handled failures enter reporting.
Capture asynchronous platform errors
PlatformDispatcher.instance.onError receives uncaught asynchronous and platform-dispatched errors outside Flutter callbacks. Record the error as fatal and return true to indicate it was handled by the callback.
This hook does not replace explicit handling for recoverable Futures.

Build production-ready Firebase Crashlytics features
The Complete Flutter Guide turns firebase crashlytics into maintainable app architecture, polished UI, and testable production code.
Enrol nowAdd the Crashlytics plugin before routing framework and dispatcher errors into Firebase reporting.
dependencies:
flutter:
sdk: flutter
firebase_crashlytics: any
Record a caught failure with its reason
recordError accepts the exception, stack trace, fatal flag, reason, and optional information. Call it inside catch with the original stack rather than StackTrace.current after the fact.
Recording without the originating stack makes grouping and diagnosis far weaker.
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
Attach keys logs and user identifiers
setCustomKey can add plan or feature state, log adds breadcrumbs, and setUserIdentifier associates an opaque account ID. Use low-cardinality values and avoid emails, tokens, names, or message bodies.
Changing keys on every frame creates noisy reports without explaining the crash.
import 'dart:ui';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:flutter/foundation.dart';
void configureCrashlytics() {
FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError;
PlatformDispatcher.instance.onError = (error, stack) {
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
return true;
};
FirebaseCrashlytics.instance.setCustomKey('app_flavour', 'production');
FirebaseCrashlytics.instance.log('Crash reporting configured');
}
My LinkedIn reliability notes discuss symbol ownership, privacy-safe context, and prioritising affected journeys.

The Complete Flutter Guide: Build Android, iOS and Web apps
Go from scratch to building industry-standard apps with Riverpod, Firebase, animations, REST APIs, and more.
Enrol nowForce one controlled test crash
FirebaseCrashlytics.instance.crash deliberately terminates the app so a non-production build can verify the pipeline. Run the built release on a device, relaunch it, and confirm the issue reaches the console after processing.
Leaving a reachable crash button in production is an obvious operational hazard.
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError;
PlatformDispatcher.instance.onError = (error, stack) {
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
return true;
};
runApp(const MyApp());
}
Verify release symbols and collection
Crash collection and symbol upload differ by platform and build mode, and debug runs may not report like release. Check Android mapping or native symbol tasks and Apple dSYM upload in the actual release pipeline.
A console with unreadable stack frames often indicates missing symbols rather than absent crashes.
try {
await payments.capture(orderId);
} catch (error, stack) {
await FirebaseCrashlytics.instance.recordError(
error,
stack,
reason: 'Payment capture failed',
information: ['orderType: subscription'],
);
rethrow;
}
Verification is complete only after a release-mode crash appears with readable Dart and native frames in the Firebase console. Run the controlled crash on a non-production build, relaunch so the cached report can upload, and inspect custom keys and breadcrumb logs. Then remove the trigger and confirm the CI release tasks still upload Android mapping files and Apple dSYMs. Also review every attached value for privacy: an opaque account identifier can support grouping, whereas email addresses and payment details do not belong in a crash report. Non-fatal reporting deserves a separate check: throw a known exception, catch it with its original stack, record a precise reason, and allow the screen to recover. The event should appear as non-fatal and should group with a second identical occurrence. If it becomes a new issue every time, inspect changing messages or keys that are fragmenting Crashlytics grouping. Record the build number as a custom key for release triage.
Turn crash reports into reproducible evidence
Flutter framework errors and uncaught asynchronous errors travel through different paths. Forward framework failures from FlutterError.onError, and capture uncaught root-isolate errors through PlatformDispatcher.instance.onError or the application’s guarded zone strategy. Mark genuinely fatal failures accordingly, but record handled operational errors as non-fatal only when they add diagnostic value. Reporting every expected network timeout will bury the crashes that need engineering attention.
Add a small set of low-cardinality custom keys such as app flavour, current feature, signed-in state, or synchronisation phase. Avoid email addresses, tokens, free-form user content, and entire request bodies. A pseudonymous internal identifier may help correlate reports only if the project’s privacy policy and retention controls support it. Breadcrumb-style logs should describe decisions and lifecycle events, not repeat secrets that another SDK already redacts.
Release builds need symbol handling that matches the chosen obfuscation and platform toolchain. Preserve the symbol files produced for each exact build and upload them through the documented pipeline; symbols from a later rebuild cannot decode an earlier binary reliably. Include a unique build number in the release record, then check that Crashlytics receives a deliberate non-production test crash before relying on the integration. Debugger attachment and collection settings can affect when a report is sent.
A useful verification pass covers a framework exception, an uncaught asynchronous exception, a manually recorded non-fatal error with its original stack, and a fatal test on every shipped platform. Restart the app so queued reports can upload, then confirm grouping, symbols, keys, and release identity in the console. Consent choices should be applied before collection begins where opt-in reporting is required. When fixing a crash, reproduce the triggering state locally and add a regression test; a falling event count is encouraging, but only a controlled test proves the underlying path no longer fails.
Crash-free metrics need context. A release with few sessions can swing dramatically, and one looping startup crash can generate many events from the same affected people. Prioritise by affected users, severity, regression status, and blocked journey rather than raw event count alone. Add ownership and a reproducible breadcrumb to the issue before marking it fixed. After deploying, keep the issue visible for the affected app versions and verify the regression test against the exact failure mechanism. If a report lacks symbols or stack frames, repair the release pipeline first; guessing from an obfuscated trace wastes time and can produce an unrelated code change.
Errors from secondary isolates need an explicit forwarding strategy because root handlers cannot infer every worker failure automatically. Keep the original error and stack together when sending them across the isolate boundary. Shutdown tests should also verify that reporting code cannot throw a new exception while handling the first one. A crash pipeline earns trust when failure capture remains simpler and more dependable than the feature code it observes.
Common mistakes
- Separate fatal and non-fatal reports: In Crashlytics reporting, flooding reports with normal user mistakes hides actionable crashes; inspect this Crashlytics reporting cause before changing another Crashlytics reporting widget.
- Route Flutter framework errors: In Crashlytics reporting, overwriting the handler later can silently disconnect Crashlytics; inspect this Crashlytics reporting cause before changing another Crashlytics reporting widget.
- Capture asynchronous platform errors: In Crashlytics reporting, this hook does not replace explicit handling for recoverable Futures; inspect this Crashlytics reporting cause before changing another Crashlytics reporting widget.
- Record a caught failure with its reason: In Crashlytics reporting, recording without the originating stack makes grouping and diagnosis far weaker; inspect this Crashlytics reporting cause before changing another Crashlytics reporting widget.
- Attach keys logs and user identifiers: In Crashlytics reporting, changing keys on every frame creates noisy reports without explaining the crash; inspect this Crashlytics reporting cause before changing another Crashlytics reporting widget.
- Force one controlled test crash: In Crashlytics reporting, leaving a reachable crash button in production is an obvious operational hazard; inspect this Crashlytics reporting cause before changing another Crashlytics reporting widget.
Frequently asked questions
How does separate fatal and non-fatal reports work in Crashlytics reporting?
For Crashlytics reporting, the starting rule is that crashlytics groups fatal crashes and recorded non-fatal errors so teams can prioritise stability. Apply this Crashlytics reporting rule first because separate fatal and non-fatal reports determines whether the Crashlytics reporting pattern fits.
Why does capture asynchronous platform errors matter for Crashlytics reporting?
In Crashlytics reporting, record the error as fatal and return true to indicate it was handled by the callback. Keeping capture asynchronous platform errors at the Crashlytics reporting call site exposes the Crashlytics reporting return value directly.
What failure should I test first in Crashlytics reporting?
First reproduce the Crashlytics reporting case where changing keys on every frame creates noisy reports without explaining the crash. A useful report retains a small stable set of keys and breadcrumbs that explains the failing journey without changing on every frame.
How can I verify Crashlytics reporting before release?
Exercise verify release symbols and collection with real Crashlytics reporting inputs on every shipped platform. Inspect the final Crashlytics reporting callback or output; a successful Crashlytics reporting button tap alone is not proof.
Further reads
Connect Crashlytics reporting to the surrounding Flutter stack through these published tutorials:
- Flutter Development Guide 2026
- Flutter Firebase Setup: Connect Android, iOS, and Web
- Flutter Handle API Errors: Try/Catch, Retries, and User Feedback
- Flutter Firebase Authentication: Email, Google, and Sign-Out
- Flutter Push Notifications With Firebase Cloud Messaging
Sources: FlutterFire and Firebase documentation; Crashlytics reporting examples verified against current stable Flutter.