MyFamilyTracker
Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.
Releasing an app without crash reporting is flying blind. Here's how to set up Firebase Crashlytics, interpret crash data, prioritize fixes, and build a monitoring workflow that catches real problems fast.
On this page
Your app is in the hands of users now. Devices you've never tested on, OS versions you don't own, usage patterns you didn't anticipate. Without crash reporting, you find out about problems when your rating drops.
With crash reporting, you find out in minutes.
Add Crashlytics to your Android project:
// project-level build.gradle.kts
plugins {
id("com.google.gms.google-services") version "4.4.1" apply false
id("com.google.firebase.crashlytics") version "3.0.1" apply false
}
// app/build.gradle.kts
plugins {
id("com.google.gms.google-services")
id("com.google.firebase.crashlytics")
}
dependencies {
implementation(platform("com.google.firebase:firebase-bom:33.0.0"))
implementation("com.google.firebase:firebase-crashlytics")
implementation("com.google.firebase:firebase-analytics") // Required for Crashlytics
}Download
google-services.jsonapp/Crashlytics captures crashes automatically after this setup. No code changes needed for basic functionality.
Default crash reports show the stack trace. Custom keys and logs provide context:
// Add user context (without PII — no emails, no names)
FirebaseCrashlytics.getInstance().apply {
setUserId(anonymizedUserId) // Non-identifying ID
setCustomKey("subscription_tier", user.tier)
setCustomKey("app_state", currentScreen)
setCustomKey("task_count", repository.getTaskCount())
}Custom log messages before a crash:
class TaskSyncService {
fun syncTasks() {
FirebaseCrashlytics.getInstance().log("Starting task sync, count: ${tasks.size}")
try {
performSync()
} catch (e: Exception) {
FirebaseCrashlytics.getInstance().log("Sync failed at step: $currentStep")
throw e // Re-throw so Crashlytics captures it
}
}
}These logs appear in the Crashlytics dashboard alongside the stack trace.
Not every exception should crash the app. But they should still be tracked:
try {
val result = analyticsService.trackEvent(event)
if (!result.isSuccess) {
FirebaseCrashlytics.getInstance().recordException(
AnalyticsException("Event tracking failed: ${result.errorCode}")
)
}
} catch (e: Exception) {
// Don't crash the app for analytics failure
FirebaseCrashlytics.getInstance().recordException(e)
Timber.e(e, "Analytics tracking failed")
}Non-fatal exceptions appear separately in Crashlytics. They don't affect your crash-free rate but reveal problems in code paths that don't crash.
Android Vitals (Play Console → Android Vitals) provides crash and ANR rates from Google's perspective. The thresholds that trigger "poor app health" badges:
These thresholds vary by category but these are common baselines. Exceeding them can reduce your Play Store visibility.
ANRs don't appear in Crashlytics automatically. You must enable ANR reporting:
FirebaseCrashlytics.getInstance().apply {
// Enabled by default in recent versions
// To explicitly enable:
setCrashlyticsCollectionEnabled(true)
}Daily monitoring (5 min):
Weekly triage:
Post-release (within 4 hours of rollout):
| User Impact | Severity | Action |
|---|---|---|
| High (> 1% users) | App unusable | Emergency hotfix within hours |
| High | Degraded experience | Next minor release |
| Low (< 0.1% users) | App unusable | Fix in next sprint |
| Low | Degraded experience | Backlog |
"High user impact" is relative to your total user count. A crash affecting 1,000 users on an app with 10,000 total users is more urgent than one affecting 1,000 on an app with 1 million.
For each new crash type, the investigation process:
// Example crash: NullPointerException in TaskAdapter
// Stack trace shows: TaskAdapter.onBindViewHolder line 47
// Root cause found: task.dueDate was null but accessed without null check
class TaskAdapter {
override fun onBindViewHolder(holder: TaskViewHolder, position: Int) {
val task = tasks[position]
holder.dueDateText.text = task.dueDate?.let { formatDate(it) } ?: "No due date" // Fixed
}
}
// Add regression test immediately
@Test
fun `task without due date displays fallback text`() {
val task = Task(id = "1", title = "No date", dueDate = null)
val adapter = TaskAdapter(listOf(task))
// ... verify "No due date" is displayed
}| App Type | Acceptable Crash-Free Rate |
|---|---|
| Production app | > 99.5% |
| High-quality apps | > 99.7% |
| Mission-critical | > 99.9% |
A 99% crash-free rate means 1 in 100 sessions crashes. If you have 10,000 daily active users, that's 100 crashed sessions per day. Not acceptable.
Sudarshan Chaudhari
AI Systems Builder / Product Engineer
Bangkok, Thailand
Solo Android developer with 13+ years in QA, building Android apps, AI automation systems, and developer tools at SudarshanTechLabs.
Related Posts
Related Apps
Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.
Building something? Available for Android dev and QA consulting.
Work with meComments — powered by Giscus
Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.
ReadSend gentle nudges, emojis, and short voice notes to say "I miss you" without chatting.
Read