Why Crash Reporting Matters for React Native Apps
Mobile applications, especially those built with React Native, face unique challenges when it comes to error detection and debugging. Unlike traditional web applications where you can monitor server logs, mobile apps distribute across thousands of devices with varying configurations, OS versions, and network conditions. A crash in production might affect only a subset of users, making it nearly impossible to reproduce and diagnose without proper tooling.
Firebase Crashlytics addresses these challenges by providing a centralized system for collecting and analyzing crash reports from your entire user base. The service automatically captures crash information, including stack traces, device information, and custom logs that help you understand exactly what happened when an error occurred. This capability is particularly valuable for React Native applications, where JavaScript errors can interact with native code in complex ways.
The service offers several key benefits that make it essential for any production React Native application. First, it provides real-time crash alerts, notifying you immediately when significant issues affect your users. Second, it aggregates similar crashes, helping you identify which problems impact the most users and deserve priority attention. Third, it correlates crashes with user actions, giving you context about what features or workflows lead to errors.
Beyond basic crash reporting, Crashlytics offers sophisticated features like custom key-value pairs for tracking specific conditions, log statements that persist even through crashes, and integration with other Firebase services for comprehensive app monitoring. These capabilities transform crash reporting from a reactive debugging tool into a proactive quality assurance system that supports your mobile app development workflow.
When implementing comprehensive error handling, consider combining Crashlytics with TypeScript generics for reusable components to build robust, type-safe error boundaries throughout your application.
Transform your app's stability with powerful crash analysis
Real-Time Alerts
Receive immediate notifications when significant crashes affect your users, enabling rapid response to production issues.
Smart Aggregation
Crash reports are automatically grouped by root cause, helping you prioritize fixes that impact the most users.
User Context
Correlate crashes with user actions to understand which features and workflows lead to errors.
Custom Analytics
Track application-specific metrics and attributes alongside crash data for deeper insights.
Prerequisites and Initial Setup
Before integrating Crashlytics into your React Native application, you need to establish the foundation by setting up Firebase and the React Native Firebase package. This section walks you through the necessary prerequisites and initial configuration steps.
Creating a Firebase Project
The first step involves creating a Firebase project and registering your application. Navigate to the Firebase console at console.firebase.google.com and create a new project. During setup, you can enable Google Analytics for additional insights, though it's not strictly required for Crashlytics functionality. Once your project is created, add an Android app and an iOS app to the project, following the platform-specific setup instructions to download the configuration files.
For Android, you'll need the google-services.json file, which should be placed in your project's android/app directory. For iOS, you'll need GoogleService-Info.plist, which should be added to your Xcode project. Both files contain unique identifiers that link your mobile app to the Firebase project and enable secure communication between your app and Firebase services.
As part of your comprehensive mobile strategy, consider how crash reporting integrates with your broader app testing and quality assurance processes.
Installing React Native Firebase
The React Native Firebase library provides the bridge between your React Native application and Firebase services. Begin by installing the core package, which is required before adding any Firebase module:
# Install the core React Native Firebase package
npm install @react-native-firebase/app
# For iOS, navigate to the ios directory and install pods
cd ios && pod install
After installing the core package, you can install Crashlytics as a separate module:
# Install the Crashlytics package
npm install @react-native-firebase/crashlytics
# For iOS, update pods
cd ios && pod install
The modular architecture of React Native Firebase means you only include the specific Firebase services your app uses, keeping your application bundle size optimized. Crashlytics is one of several available modules, including Analytics, Auth, Firestore, and Cloud Messaging.
To optimize your app's performance alongside robust error handling, explore React dynamic imports and route-centric code splitting to reduce initial bundle size and improve load times.
Platform-Specific Installation: Android
Android requires a few additional steps beyond the basic package installation. The primary requirements involve configuring the Android manifest and ensuring the appropriate Firebase dependencies are present in your Gradle files.
Gradle Configuration
In your android/build.gradle file, ensure you have the appropriate classpath for the Google services plugin:
buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:8.1.0'
classpath 'com.google.gms:google-services:4.4.0'
}
}
In your android/app/build.gradle, apply the Google services plugin and add the Crashlytics configuration:
apply plugin: 'com.google.gms.google-services'
android {
compileSdkVersion 34
defaultConfig {
multiDexEnabled true
}
}
dependencies {
implementation platform('com.google.firebase:firebase-bom:32.7.0')
implementation 'com.google.firebase:firebase-crashlytics'
implementation 'com.google.android.gms:play-services-base:18.2.0'
}
The Crashlytics plugin requires the NDK (Native Development Kit) for native crash reporting, which is automatically included through the Firebase BoM (Bill of Materials) when you add the Crashlytics dependency. This native component captures low-level crash information that JavaScript-level error handling cannot access.
Platform-Specific Installation: iOS
For iOS, the configuration process involves both CocoaPods setup and Xcode project configuration. Ensure your Podfile includes the Crashlytics pod:
platform :ios, '13.0'
target 'YourApp' do
pod 'Firebase/Crashlytics', '~> 10.18'
pod 'FirebaseCore', '~> 11.0'
end
Xcode Configuration
In your iOS project, you need to add a Run Script Build Phase to upload debug symbols. This script is crucial because Crashlytics requires dSYM (debug symbol) files to translate raw crash addresses into human-readable stack traces:
"${PODS_ROOT}/FirebaseCrashlytics/run"
Additionally, you need to disable the Crashlytics debug collection during development by adding a User-Defined Setting in Xcode called FLT_CRASHLYTICS_DEBUG_ENABLED and setting it to NO for your Debug configuration.
Proper iOS configuration ensures your cross-platform mobile solution delivers reliable performance across all Apple devices.
For comprehensive network error handling in your React Native app, learn how to intercept JavaScript Fetch API requests and responses to capture and report network-related errors alongside native crashes.
Firebase.json Configuration
The firebase.json file in your project root serves as the central configuration hub for React Native Firebase, including Crashlytics settings. Understanding these configuration options is essential for controlling how Crashlytics behaves in different environments.
Core Configuration Options
{
"react-native": {
"crashlytics_debug_enabled": true,
"crashlytics_disable_auto_disabler": true,
"crashlytics_auto_collection_enabled": true,
"crashlytics_is_error_generation_on_js_crash_enabled": true,
"crashlytics_javascript_exception_handler_chaining_enabled": true
}
}
Configuration Explanation
| Option | Description |
|---|---|
crashlytics_debug_enabled | Controls whether Crashlytics operates during development |
crashlytics_disable_auto_disabler | Prevents automatic disabling under certain conditions |
crashlytics_auto_collection_enabled | Automatically collects crash reports |
crashlytics_is_error_generation_on_js_crash_enabled | Captures JavaScript-level crashes |
crashlytics_javascript_exception_handler_chaining_enabled | Chains to React Native's default exception handler |
The crashlytics_debug_enabled option controls whether Crashlytics operates during development. When set to true, Crashlytics will collect and report crashes even when running in debug mode. During development, you typically want this enabled so you can verify your integration is working correctly.
Basic API Usage
Once Crashlytics is properly configured, you can begin using its API to enhance your crash reports and capture application-specific information. The API provides methods for logging events, setting user identifiers, adding custom attributes, and manually recording errors.
Importing Crashlytics
import crashlytics from '@react-native-firebase/crashlytics';
The module is automatically initialized when your app starts, provided you've correctly configured the Firebase core package. You don't need to manually initialize Crashlytics in most cases.
Logging Events and Context
The log() method allows you to record custom log messages that are attached to crash reports. These logs appear in the Firebase console alongside the crash information, providing context about what the user was doing when the crash occurred:
async function signInUser(email, password) {
crashlytics().log('User attempting to sign in');
try {
const userCredential = await auth().signInWithEmailAndPassword(email, password);
crashlytics().log('User sign-in successful');
await crashlytics().setUserId(userCredential.user.uid);
return userCredential;
} catch (error) {
crashlytics().log('User sign-in failed');
throw error;
}
}
Log messages are limited in size and quantity, so use them strategically to provide meaningful context rather than verbose debugging output.
Setting User Identifiers
The setUserId() method associates crash reports with specific users:
await crashlytics().setUserId(user.id);
User IDs should be non-personally-identifiable identifiers like internal user numbers or hashed email addresses.
Custom Attributes
// Set a single attribute
await crashlytics().setAttribute('subscription_tier', 'premium');
// Set multiple attributes at once
await crashlytics().setAttributes({
app_version: '2.1.0',
build_number: '142',
environment: 'production'
});
Recording Non-Fatal Errors
try {
await processUserData(data);
} catch (error) {
crashlytics().recordError(error);
showFallbackUI();
}
Testing Your Integration
Verifying that Crashlytics is correctly configured is crucial before deploying to production. This section describes a systematic approach to testing your integration on both Android and iOS.
Creating a Test Crash Button
For testing purposes, add a button to your application that intentionally triggers a crash:
import { View, Button } from 'react-native';
import crashlytics from '@react-native-firebase/crashlytics';
function CrashTestScreen() {
return (
<View>
<Button
title="Test Crash"
onPress={() => crashlytics().crash()}
/>
</View>
);
}
Verifying Android Integration
adb logcat | grep -i crash
When your app starts, look for messages indicating that Crashlytics has initialized. After triggering a test crash, you should see log messages indicating that the crash was handled.
Verifying iOS Integration
xcrun simctl spawn booted log stream --level debug --style compact | grep -i crash
Important: Debuggers intercept crash reports on iOS. Start the app from the simulator directly rather than from Xcode to see crash reports in the Firebase console.
For comprehensive API request monitoring, consider implementing rate limiting in Node.js patterns to protect your backend services and improve overall application reliability.
Debug vs Production Configuration
Transitioning from development to production requires adjusting Crashlytics configuration to reduce noise and ensure reports are actionable.
Recommended Production Settings
{
"react-native": {
"crashlytics_debug_enabled": false,
"crashlytics_disable_auto_disabler": true,
"crashlytics_auto_collection_enabled": true,
"crashlytics_is_error_generation_on_js_crash_enabled": true,
"crashlytics_javascript_exception_handler_chaining_enabled": false
}
}
Understanding Exception Handler Chaining
The crashlytics_javascript_exception_handler_chaining_enabled setting controls whether Crashlytics hooks into React Native's global exception handler.
Without exception handler chaining, React Native's default behavior is to perform a native crash when an unhandled JavaScript exception occurs, creating a "Fatal" crash report without the JavaScript stack trace.
With exception handler chaining enabled, Crashlytics can capture the JavaScript stack trace and report it as a "Non-Fatal" crash. However, this can result in duplicate reports.
The recommended approach is to disable exception handler chaining in production. This means you get one clean "Fatal" crash report for each JavaScript exception.
User Opt-Out and Privacy
Respecting user privacy is essential, and Crashlytics provides mechanisms for users to opt out of crash reporting.
Checking Collection Status
const collectionEnabled = await crashlytics().isCrashlyticsCollectionEnabled();
Disabling Collection
Users who prefer not to send crash reports can be given the option to disable collection:
async function disableCrashReporting() {
await crashlytics().setCrashlyticsCollectionEnabled(false);
}
Privacy Settings Implementation
Consider implementing a privacy settings screen where users can toggle crash reporting:
import { useState, useEffect } from 'react';
import { View, Switch, Text } from 'react-native';
import crashlytics from '@react-native-firebase/crashlytics';
function PrivacySettings() {
const [crashReportingEnabled, setCrashReporting] = useState(true);
useEffect(() => {
crashlytics().isCrashlyticsCollectionEnabled().then(setCrashReporting);
}, []);
const toggleCrashReporting = async (value) => {
await crashlytics().setCrashlyticsCollectionEnabled(value);
setCrashReporting(value);
};
return (
<View>
<Text>Help us improve by sending crash reports</Text>
<Switch
value={crashReportingEnabled}
onValueChange={toggleCrashReporting}
/>
</View>
);
}
When implementing opt-out functionality, be sure to store the user's preference persistently and clearly communicate what data is collected and how it's used.
Best Practices and Common Pitfalls
Meaningful Log Statements
Log statements should provide context that helps you understand user behavior leading up to crashes. Avoid logging sensitive information like passwords or personal data.
Poor: crashlytics().log('Processing data');
Effective: crashlytics().log('Processing order #12345 for user premium_member');
Attribute Strategy
Custom attributes should be values that help you filter and segment crashes. Consider attributes like user subscription tier, app version, build number, and environment. Avoid attributes with high cardinality.
Avoid False Positives
Be cautious about triggering test crashes or using Crashlytics in automated tests. Consider disabling Crashlytics during automated testing.
Symbolication and dSYM Files
For iOS, ensure your build process correctly generates and uploads dSYM files. Without dSYM files, crash reports will show only memory addresses rather than readable function names and line numbers.
Monitoring and Alerting
Configure Firebase alerts to notify you immediately when significant crashes occur. Set up thresholds for crash-free users and total crash counts. Proactive alerting enables rapid response to production issues.
Integrating crash reporting into your broader software development lifecycle helps maintain high-quality mobile applications.
For building truly reusable error handling components, explore our guide on 5 ways to implement typing animation in React and apply similar patterns to create consistent, type-safe error handling utilities across your codebase.
Frequently Asked Questions
How long does it take for crash reports to appear in the Firebase console?
Crash reports typically appear within a few minutes after the app restarts following a crash. During initial setup, it may take longer for the first reports to appear while Firebase processes your app's dSYM files.
Can I use Crashlytics with Expo?
Yes, Expo supports Firebase Crashlytics through the @react-native-firebase/crashlytics package. However, you'll need to use the Expo development build or prebuild workflow to include native code dependencies.
Does Crashlytics work for JavaScript errors only?
No, Crashlytics captures both JavaScript errors and native crashes. Native crashes occur in the iOS or Android runtime, while JavaScript errors happen in the JavaScript thread. Both types are reported to the Firebase console.
How do I disable Crashlytics during development?
Set `crashlytics_debug_enabled` to false in your firebase.json file. This prevents development crashes from cluttering your production crash reports while still allowing you to verify the integration works.
Sources
-
React Native Firebase Crashlytics Documentation - Official React Native Firebase documentation providing comprehensive coverage of Crashlytics installation, configuration, and usage patterns.
-
Firebase Crashlytics Get Started Guide - Google's official Firebase Crashlytics documentation providing platform-agnostic setup guidance and best practices.
-
Invertase: Installing and Configuring React Native Firebase Crashlytics - Detailed configuration guide from the maintainers of React Native Firebase.