Skip to main content

Firebase Crashlytics

Crashlytics collects crashes from our Flutter mobile apps on Android and iOS. This page covers how to make crash reports readable, how to read the crash-free metric, and how to tie a crash to the exact build a user has.

Firebase facts on this page were checked on 2026-10-19 against the official docs linked at the bottom. Firebase changes; re-check before you rely on a number.

How it works​

Obfuscation scrambles class and function names in the release binary. It makes the app harder to reverse-engineer. The cost is that a crash trace is unreadable until Crashlytics has the matching symbol files, which map the scrambled names back. Symbols must come from the same build as the binary. A rebuild makes new symbols.

How to build it​

Build a release with symbols​

flutter build appbundle --release --obfuscate --split-debug-info=build/symbols
flutter build ipa --release --obfuscate --split-debug-info=build/symbols

Keep build/symbols with the build artifact. If you lose it, crashes from that build stay unreadable forever.

Android​

Apply the Crashlytics Gradle plugin in the app module. When R8 obfuscation is on, the plugin uploads the R8 mapping file for you. You can switch this per build type with mappingFileUploadEnabled. Turn it off only for builds nobody will debug.

Dart symbols need a separate upload with the Firebase CLI. Use the Firebase app id (mobilesdk_app_id in google-services.json), not the package name. The path is the same folder you passed to --split-debug-info.

firebase crashlytics:symbols:upload --app=1:123456789012:android:0123456789abcdef build/symbols

iOS​

The Runner target needs a build phase that uploads dSYMs (debug symbol files for Apple builds). The FlutterFire tooling adds one; check it is there and is the last phase. Without dSYMs the console shows a "Missing dSYM" alert and holds the crashes until you upload them.

A third-party framework that ships without a dSYM gives an "Upload Symbols Failed" warning for that framework only. It does not block the upload. Your own frames still symbolicate.

Version name and build number​

In pubspec.yaml, version: 1.4.0+52 means version name 1.4.0 and build number 52. The version name is what testers, the store, and crash reports show people.

  • Bump the build number on every build.
  • Bump the version name too: patch for a fix, minor for a feature.
  • Name the artifact after the full version, for example example-app-1.4.0-52.aab.
  • Check the result before you upload. aapt2 dump badging app.apk shows both values.

Fatal versus non-fatal​

Crash-free metrics count fatal events only. Decide on purpose what is fatal:

  • An uncaught error that breaks the screen is fatal.
  • A network error because the phone went offline is not a crash. Record it as non-fatal, or it sinks the crash-free metric and hides real crashes.
  • Debug builds send nothing. Dev-cycle crashes drown the real signal.

If you also run Sentry on mobile, chain the error handlers. Install Crashlytics so it calls the previous handler. Never replace it, or one tool goes silent.

Reading the metrics​

MetricMeaning
Crash-free usersShare of users in the period who had no crash. A user is one install on one device.
Crash-free sessionsShare of sessions that did not end in a crash. A new session starts on a cold start, or after 30 minutes in the background.

Both are totals over the period you pick, not an average. A longer period usually shows a lower crash-free users number, so only compare the same period length.

Alerts​

AlertDefault
Regressed issue (a closed issue came back in a newer version)On
Trending issues digestOn
New fatal, new non-fatal, new ANROff until you choose a channel
Velocity (one issue hits many users fast)Off until you choose a channel. Default threshold 1% of sessions and 25 users in a 30-minute window, once per issue per app version

Turn on new fatal and velocity alerts for every production app. Send them to the team channel.

Rules​

Rule

Upload symbols for every release build, and keep them with the artifact. A crash from a build without symbols cannot be read, and you cannot make the symbols again later.

Rule

Bump the version name, not just the build number. Crash reports and the store show the name. If every build says the same name, you cannot tell which build a tester or user has.

Rule

Never log tokens, emails, names, chat content or full deep links. Log ids, booleans, lengths and URL paths. Crashlytics keeps a buffer of recent log lines and sends it with each crash.

Mistakes we made​

  • Seventeen builds all said 0.1.0. Only the build number moved. The store, the device info screen and crash reports all looked identical. Bump the version name every time.
  • The upload tool renumbered the build. Xcode Organizer can manage the build number for you and pick the next free one. A build-38 archive shipped as 39. Check which archive you selected, not just the number in the store console.
  • Offline errors can count as crashes. The usual setup sends every uncaught Flutter error as fatal, including an image load that fails because the phone is offline. That drags crash-free users down for no real bug. We record connectivity errors as non-fatal.

Checklist​

  • Release builds use --obfuscate --split-debug-info, and the symbols folder is archived.
  • Android: Crashlytics Gradle plugin applied, mapping upload on for release.
  • Dart symbols uploaded with firebase crashlytics:symbols:upload for each build.
  • iOS: dSYM upload build phase present, no "Missing dSYM" alert.
  • Version name and build number both bumped, artifact named after them.
  • Offline and network errors recorded as non-fatal.
  • New fatal and velocity alerts sent to the team channel.
  • Day after a rollout: crash-free users for the new version checked on both platforms.

Sources​