How do you set up crash reporting and monitoring for a production mobile app — Crashlytics, Sentry?
Fatal and non-fatal errors, symbolicated stack traces, and a crash-free rate wired in as the guardrail that gates a staged rollout.
LEARN BY DOINGPut your intuition to work3 steps
How do you set up crash reporting and monitoring for a production mobile app — Crashlytics, Sentry?
Use the lesson’s examples and explanation to check your idea.
Personal reflection. Nothing is sent to an AI service.Crash reporting isn't a nice-to-have bolted on after launch — it's the feedback loop that makes staged rollouts and feature flags meaningful in the first place. If you can't see crash-free rate move in near real time, "halt the rollout at 20%" is a guess, not a decision. The two tools most teams reach for are Firebase Crashlytics and Sentry, and they overlap more than their marketing suggests, so an interviewer is usually listening for whether you understand what each is actually giving you underneath the dashboard.
#Fatal vs. non-fatal, and why you want both
A fatal crash report is generated when the app terminates unexpectedly: a native crash, an uncaught JS exception in React Native, a fatal signal. A non-fatal is something you explicitly report from a catch block: an API call that failed and fell back to cached data, a parsing error that was handled, a payment that had to be retried. Teams that only wire up fatal crash reporting are flying blind on the failures that matter most to the product even though they didn't take the app down — a payment provider silently returning malformed responses for an hour is a five-alarm incident that a fatal-only dashboard will never surface.
#Symbolication: the step that makes any of this readable
A raw stack trace off a release build is a list of memory addresses or obfuscated names, not function names and line numbers — useless without a matching symbol file. On iOS that's the dSYM bundle generated per build, which Crashlytics or Sentry needs uploaded — via a build phase script, or sentry-cli upload-dif — matched to the exact build UUID. On Android with R8/ProGuard code shrinking enabled, which is effectively always true in a release build, that's the mapping.txt file the Gradle plugin can upload automatically. Skip this step and every crash report from production is unreadable hex, discovered only once someone needs it during an incident.
class PaymentRepository(private val crashlytics: FirebaseCrashlytics) {fun processPayment(orderId: String, provider: String) {crashlytics.setCustomKey("order_id", orderId)crashlytics.setCustomKey("payment_provider", provider)crashlytics.log("payment: starting charge for $orderId")try {chargeCard(orderId)} catch (e: PaymentDeclinedException) {// Handled, user-facing — still worth knowing about in aggregate.crashlytics.recordException(e)showDeclinedDialog()} catch (e: PaymentGatewayTimeoutException) {crashlytics.recordException(e)retryWithBackoff(orderId)}}}
android {buildTypes {release {isMinifyEnabled = trueproguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")}}}firebaseCrashlytics {mappingFileUploadEnabled = truenativeSymbolUploadEnabled = true}
#Sentry: crashes plus performance and release health
Sentry covers the same fatal/non-fatal split but adds two things teams often reach for it specifically because of: performance monitoring — traces for cold start time, screen load, a slow network call, sampled at a rate you control so it doesn't blow up event volume — and release health, which ties crash-free session and user rate directly to a release version and session duration, not just a raw crash count. That's the metric you actually want when deciding whether to advance a staged rollout.
import * as Sentry from "@sentry/react-native";Sentry.init({dsn: "https://examplePublicKey@o0.ingest.sentry.io/0",release: `com.myapp@${appVersion}+${buildNumber}`,tracesSampleRate: 0.2,beforeSend(event) {// Strip anything that could identify a person before it leaves the device.if (event.user) {delete event.user.email;delete event.user.ip_address;}return event;},});export function identifyUser(hashedUserId: string) {Sentry.setUser({ id: hashedUserId });}export function trackCheckoutStep(step: string) {Sentry.addBreadcrumb({ category: "checkout", message: step, level: "info" });}
#Turning crash-free rate into a release gate
The payoff for wiring this up carefully is that it becomes the guardrail metric deciding whether a staged rollout advances: hold at 20% and page someone if crash-free sessions drop more than half a point versus the previous release, or automate it entirely — a scheduled job reads the Crashlytics or Sentry API and calls the Play Developer API or App Store Connect API to halt the rollout itself before a human even looks at a dashboard.