Pular para o conteúdo

Firebase Crashlytics Cheat Sheet

Overview

Firebase Crashlytics is a lightweight, real-time crash reporting tool that helps mobile developers track, prioritize, and fix stability issues affecting app quality. It automatically groups crashes into manageable issues, highlights the severity based on user impact, and provides detailed stack traces with device context for each crash. Crashlytics integrates deeply with the Firebase ecosystem and is used by millions of apps to maintain stability and improve user experience.

Crashlytics goes beyond basic crash reporting by providing crash-free user and session metrics, velocity alerts for sudden stability regressions, custom logging and key-value pairs for debugging context, non-fatal error tracking, and ANR (Application Not Responding) detection for Android. It supports both iOS (Swift/Objective-C) and Android (Kotlin/Java), works with both native and cross-platform frameworks like React Native and Flutter, and provides BigQuery export for custom analysis and dashboards.

Installation (Android)

// Project-level build.gradle.kts
plugins {
    id("com.google.gms.google-services") version "4.4.2" apply false
    id("com.google.firebase.crashlytics") version "3.0.2" apply false
}

// App-level build.gradle.kts
plugins {
    id("com.android.application")
    id("com.google.gms.google-services")
    id("com.google.firebase.crashlytics")
}

dependencies {
    implementation(platform("com.google.firebase:firebase-bom:33.6.0"))
    implementation("com.google.firebase:firebase-crashlytics")
    implementation("com.google.firebase:firebase-analytics")
}
# Download google-services.json from Firebase Console
# Place in app/ directory

# Verify setup
./gradlew assembleDebug
# Look for "FirebaseCrashlytics" in logcat

Installation (iOS)

# Podfile
pod 'FirebaseCrashlytics'
pod 'FirebaseAnalytics'
pod install
// AppDelegate.swift or App init
import Firebase

@main
struct MyApp: App {
    init() {
        FirebaseApp.configure()
    }
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}
# Download GoogleService-Info.plist from Firebase Console
# Add to Xcode project (ensure it's in the target)

# Add Run Script build phase for dSYM upload
# Build Phases > + > New Run Script Phase
# Script:
"${BUILD_DIR%/Build/*}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run"

# Or with CocoaPods:
"${PODS_ROOT}/FirebaseCrashlytics/run"

# Input Files:
# ${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}
# ${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${PRODUCT_NAME}
# ${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist
# $(TARGET_BUILD_DIR)/$(UNLOCALIZED_RESOURCES_FOLDER_PATH)/GoogleService-Info.plist

Basic Usage (Android / Kotlin)

import com.google.firebase.crashlytics.FirebaseCrashlytics

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        
        // Optional: disable during development
        FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(!BuildConfig.DEBUG)
    }
}

// Force a test crash
fun forceCrash() {
    throw RuntimeException("Test Crashlytics crash")
}

// Log custom keys for debugging context
FirebaseCrashlytics.getInstance().apply {
    setCustomKey("user_level", 5)
    setCustomKey("screen", "checkout")
    setCustomKey("cart_items", 3)
    setCustomKey("payment_method", "credit_card")
    setCustomKey("app_theme", "dark")
}

// Set user identifier
FirebaseCrashlytics.getInstance().setUserId("user_12345")

// Log custom messages (breadcrumbs)
FirebaseCrashlytics.getInstance().log("User tapped checkout button")
FirebaseCrashlytics.getInstance().log("Payment processing started")
FirebaseCrashlytics.getInstance().log("API response: status 200")

// Record non-fatal exceptions
try {
    riskyOperation()
} catch (e: Exception) {
    FirebaseCrashlytics.getInstance().recordException(e)
    // Handle gracefully
}

// Record non-fatal with custom message
try {
    parseResponse(data)
} catch (e: JsonSyntaxException) {
    FirebaseCrashlytics.getInstance().apply {
        log("Failed to parse API response")
        setCustomKey("response_data", data.take(500))
        recordException(e)
    }
}

Basic Usage (iOS / Swift)

import FirebaseCrashlytics

// Force test crash
func forceCrash() {
    fatalError("Test Crashlytics crash")
}

// Custom keys
Crashlytics.crashlytics().setCustomValue(5, forKey: "user_level")
Crashlytics.crashlytics().setCustomValue("checkout", forKey: "screen")
Crashlytics.crashlytics().setCustomValue(3, forKey: "cart_items")

// User identifier
Crashlytics.crashlytics().setUserID("user_12345")

// Log messages
Crashlytics.crashlytics().log("User tapped checkout button")
Crashlytics.crashlytics().log("Payment processing started")

// Record non-fatal errors
do {
    try riskyOperation()
} catch {
    Crashlytics.crashlytics().record(error: error)
}

// Record with custom NSError
let error = NSError(domain: "com.example.myapp",
                     code: 1001,
                     userInfo: [NSLocalizedDescriptionKey: "Payment failed",
                                "payment_id": "pay_abc123",
                                "amount": 29.99])
Crashlytics.crashlytics().record(error: error)

// Opt-in/out of collection
Crashlytics.crashlytics().setCrashlyticsCollectionEnabled(true)

Custom Keys Reference

Key TypeAndroidiOS
StringsetCustomKey("key", "value")setCustomValue("value", forKey: "key")
BooleansetCustomKey("key", true)setCustomValue(true, forKey: "key")
IntsetCustomKey("key", 42)setCustomValue(42, forKey: "key")
FloatsetCustomKey("key", 3.14f)setCustomValue(3.14, forKey: "key")
LongsetCustomKey("key", 123L)setCustomValue(123, forKey: "key")
Max keys64 per crash64 per crash
Max key length1024 chars1024 chars
Max value length1024 chars1024 chars

ANR Detection (Android)

// ANR detection is automatic on Android
// Crashlytics detects:
// - Main thread blocked for 5+ seconds
// - Input dispatching timeout
// - BroadcastReceiver timeout

// To reduce ANRs:
// 1. Move heavy work off main thread
viewModelScope.launch(Dispatchers.IO) {
    val result = heavyComputation()
    withContext(Dispatchers.Main) {
        updateUI(result)
    }
}

// 2. Use WorkManager for background tasks
val request = OneTimeWorkRequestBuilder<SyncWorker>()
    .setConstraints(Constraints.Builder()
        .setRequiredNetworkType(NetworkType.CONNECTED)
        .build())
    .build()
WorkManager.getInstance(context).enqueue(request)

React Native Integration

# Install
npm install @react-native-firebase/app @react-native-firebase/crashlytics
cd ios && pod install
import crashlytics from '@react-native-firebase/crashlytics';

// Force crash (for testing)
crashlytics().crash();

// Log message
crashlytics().log('User signed in');

// Set user ID
crashlytics().setUserId('user_12345');

// Custom attributes
await crashlytics().setAttribute('screen', 'checkout');
await crashlytics().setAttributes({
    cart_items: '3',
    payment_method: 'credit_card'
});

// Record error
try {
    await fetchData();
} catch (error) {
    crashlytics().recordError(error);
}

// JavaScript error handler
const errorHandler = (error, isFatal) => {
    crashlytics().log(`JS Error: ${error.message}`);
    crashlytics().recordError(error);
};

// Set global error handler
ErrorUtils.setGlobalHandler(errorHandler);

// Enable/disable
await crashlytics().setCrashlyticsCollectionEnabled(true);

Flutter Integration

# pubspec.yaml
dependencies:
  firebase_core: ^3.6.0
  firebase_crashlytics: ^4.1.3
import 'package:firebase_crashlytics/firebase_crashlytics.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  
  // Catch Flutter framework errors
  FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError;
  
  // Catch async errors
  PlatformDispatcher.instance.onError = (error, stack) {
    FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
    return true;
  };
  
  runApp(MyApp());
}

// Custom keys
FirebaseCrashlytics.instance.setCustomKey('screen', 'checkout');
FirebaseCrashlytics.instance.setUserIdentifier('user_12345');
FirebaseCrashlytics.instance.log('Payment started');

// Record non-fatal
try {
  await riskyOperation();
} catch (e, stack) {
  FirebaseCrashlytics.instance.recordError(e, stack);
}

Configuration

<!-- AndroidManifest.xml - Disable auto-collection -->
<meta-data
    android:name="firebase_crashlytics_collection_enabled"
    android:value="false" />
<!-- iOS Info.plist - Disable auto-collection -->
<key>FirebaseCrashlyticsCollectionEnabled</key>
<false/>
// Enable at runtime (after user consent)
FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(true)

BigQuery Export

-- Query crash data in BigQuery
-- Enable in Firebase Console: Crashlytics > BigQuery

-- Top crashes by user impact
SELECT
  issue_id,
  issue_title,
  COUNT(DISTINCT installation_uuid) as affected_users,
  COUNT(*) as crash_count,
  MIN(event_timestamp) as first_seen,
  MAX(event_timestamp) as last_seen
FROM `project.firebase_crashlytics.events_*`
WHERE event_type = 'crash'
  AND _TABLE_SUFFIX BETWEEN '20250101' AND '20250131'
GROUP BY issue_id, issue_title
ORDER BY affected_users DESC
LIMIT 20;

-- Crash-free rate by app version
SELECT
  application.display_version,
  COUNT(DISTINCT CASE WHEN event_type = 'crash' THEN installation_uuid END) as crashed_users,
  COUNT(DISTINCT installation_uuid) as total_users,
  1 - (COUNT(DISTINCT CASE WHEN event_type = 'crash' THEN installation_uuid END) * 1.0 / COUNT(DISTINCT installation_uuid)) as crash_free_rate
FROM `project.firebase_crashlytics.events_*`
WHERE _TABLE_SUFFIX >= '20250101'
GROUP BY application.display_version
ORDER BY application.display_version DESC;

Advanced Usage

// Structured error handling with Crashlytics
class CrashReporter {
    private val crashlytics = FirebaseCrashlytics.getInstance()
    
    fun setUserContext(userId: String, properties: Map<String, String>) {
        crashlytics.setUserId(userId)
        properties.forEach { (key, value) ->
            crashlytics.setCustomKey(key, value)
        }
    }
    
    fun logBreadcrumb(message: String) {
        crashlytics.log("[${System.currentTimeMillis()}] $message")
    }
    
    fun recordError(
        throwable: Throwable,
        context: Map<String, String> = emptyMap()
    ) {
        context.forEach { (key, value) ->
            crashlytics.setCustomKey(key, value)
        }
        crashlytics.recordException(throwable)
    }
    
    fun recordNetworkError(
        url: String,
        statusCode: Int,
        error: Throwable
    ) {
        crashlytics.setCustomKey("failed_url", url)
        crashlytics.setCustomKey("status_code", statusCode)
        crashlytics.log("Network error: $url returned $statusCode")
        crashlytics.recordException(error)
    }
}

// Coroutine exception handler
val crashlyticsHandler = CoroutineExceptionHandler { _, throwable ->
    FirebaseCrashlytics.getInstance().recordException(throwable)
}

viewModelScope.launch(crashlyticsHandler + Dispatchers.IO) {
    // This crash will be recorded as non-fatal
    fetchData()
}

CLI and dSYM Upload

# Upload dSYMs manually (iOS)
# Using Firebase CLI
firebase crashlytics:symbols:upload --app=APP_ID path/to/dSYMs

# Using upload-symbols script
./Pods/FirebaseCrashlytics/upload-symbols \
  -gsp GoogleService-Info.plist \
  -p ios \
  path/to/MyApp.dSYM

# Android mapping file upload (ProGuard/R8)
# Automatic when using Firebase Crashlytics Gradle plugin
# Manual upload:
firebase crashlytics:mappingfile:upload \
  --app=APP_ID \
  --resource-file=app/build/outputs/mapping/release/mapping.txt

Troubleshooting

IssueSolution
Crashes not appearingForce a test crash; wait 5 minutes; check Firebase Console
Symbolication missingUpload dSYMs (iOS) or mapping file (Android); verify build phase
”Crashlytics not initialized”Ensure FirebaseApp.configure() called before any Crashlytics API
Duplicate crash reportsCheck for multiple recordException calls; review error handling flow
Crash-free rate incorrectWait 24 hours for metric to stabilize; check user sampling
ANRs not detectedOnly on Android 11+; ensure app targets API 30+
BigQuery export emptyEnable export in Console; data starts flowing after activation
ProGuard obfuscating crash logsEnable Crashlytics Gradle plugin; verify mapping file upload
Too many non-fatal eventsLimit recordException calls; sample high-frequency errors
Debug crashes showingDisable collection in debug: setCrashlyticsCollectionEnabled(!BuildConfig.DEBUG)