All posts Mobile Development

How I Read a Crash Report

Crash reports are the most valuable data you get after a release. But a report nobody reads is worthless — knowing what order to look at things in, and what is genuinely urgent, is a skill of its own.

DT
Demir Taşdemir Mobile App & Web Developer
— min read

A user will not tell you the app crashed. They delete it and never open it again. That is why crash reports are the most valuable data you have after a release — and most developers either never look at them or look at them in the wrong order.

Below is the routine I follow after releasing my own apps.

1. First, make sure the report is readable

This is the step that makes everything else pointless if you skip it. A release build is minified and obfuscated, and the crash traces come back that way too. If what you have looks like this, you cannot read the report:

at a.b.c.d(Unknown Source:2)
at a.b.e.f(Unknown Source:11)

Fixing this means uploading the symbol files: on Android the mapping file (the R8/ProGuard output), on iOS the dSYM. Both are generated separately for every build and are specific to that build.

Don't try to remember the upload after the release is out

If you leave the symbol file upload as a manual step, sooner or later it gets forgotten and every report from that build is garbage. Put the upload step inside your release script.

2. Look at affected users, not at the crash count

This is the most common mistake in prioritization. An issue showing "1,240 crashes" in the dashboard may all be coming from one user's device stuck in a loop. Another issue showing "38 crashes" may be hitting 38 separate users.

The order I go through:

  1. Number of affected users — how many people got hurt?
  2. Crash-free user rate — the overall health of the release.
  3. New or old — a crash that arrived with the latest release is always a priority.
  4. Where it happens — a crash at launch is far more serious than one on a screen buried deep in the app.

A crash at launch beats everything else. It means the user cannot use the app at all; you halt the rollout of that release and ship a fix.

3. Look at the clustering: what do they have in common?

The distribution table next to a crash report sometimes tells you more than the stack trace does. The breakdowns I check:

BreakdownWhat it tells you
On a single device modelA hardware-specific problem — camera, memory, screen ratio
On a single OS versionAn API behavior change
Only in one countryCaused by language, time zone or region settings
Only on low-memory devicesMemory pressure, loading large images
While the app is in the backgroundA lifecycle issue, the process being killed and restored

The "only in one country" row has a special place for me — that is exactly how I once found out that a bug was caused entirely by the device's language.

4. Read the stack trace from your own code, not from the top

The top of a stack trace is usually a system library and tells you nothing. Find the first line where your own package name appears — that is where the error was triggered.

Then look at how execution got to that line. The cause of a crash is usually not the line that crashed, but the wrong value that reached it.

5. If you can't reproduce it, leave breadcrumbs

Some crashes simply refuse to reproduce locally. When that happens, leaving breadcrumbs in the app gets you an answer faster than changing code on a guess.

// Add the steps the user went through to the report
Crashlytics.log("Sipariş ekranı açıldı, id=" + siparisId)
Crashlytics.setCustomKey("odeme_yontemi", secilenYontem)

In the next crash report those breadcrumbs show up next to the stack trace, and the path the user took becomes visible. You have to be careful not to write personal data — things like email, name or location should never go into these logs.

6. Record the errors that don't crash, too

There are errors that don't crash the app but do break it: a failed network request, data that comes back empty, an exception that gets swallowed silently. Sending those to the reporting tool as well makes it much easier to understand why a user is complaining.

try {
    veriyiKaydet()
} catch (e: Exception) {
    // The app keeps running, but we want to know what happened
    Crashlytics.recordException(e)
    kullaniciyaHataGoster()
}

My weekly routine

While I have an app live, once a week I check these:

  • Has the crash-free user rate dropped compared to last week?
  • Is there a new crash that arrived with the latest release?
  • What are the top three issues by affected users?
  • Does any store review mention a crash?

It is half an hour of work. But when you skip it, you lose users quietly and only figure out months later why the downloads went down.

A crash report is the complaint letter the user never wrote to you. Until you read it, it doesn't count as sent.

  • Crashlytics
  • Crash analysis
  • Debugging
  • Post-release
  • Quality
Share: LinkedIn X WhatsApp
DT

Demir Taşdemir

Mobile App & Web Developer

I have been building software since 2018. I have shipped 11 apps on the App Store and Google Play; right now I am working on 6 mobile apps, 1 e-commerce platform and 1 desktop game.

Someone who owns the post-release phase too

Keeping an app alive after it ships is as much a part of the job as shipping it.