Firebase Crash Reporting Examples
A step by step Firebase Crash Reporting example.
Firebase Crash Reporting
This is a Firebase Crash Reporting quickstart example. Here is the screenshot of what is created:
Step 1. Dependencies
We need to add some dependencies in our app/build.gradle
file as shown below:
(a). build.gradle
Our app-level
build.gradle
.
We Prepare our dependencies as shown below. You may use later versions.
We will enable view binding by setting the viewBinding
property to true.
We then declare our app dependencies under the dependencies
closure. We will need the following 8 dependencies:
- Our
Activity-ktx
library. - Our
Firebase-bom
library. - Our
Com.google.firebase
library. - Our
Com.google.firebase
library. - Our
Play-services-base
library.
Here is our full app/build.gradle
:
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'com.google.gms.google-services'
id 'com.google.firebase.crashlytics'
}
check.dependsOn 'assembleDebugAndroidTest'
android {
compileSdkVersion 33
defaultConfig {
applicationId "com.google.samples.quickstart.crash"
minSdkVersion 19
targetSdkVersion 33
versionCode 1
versionName "1.0"
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
minifyEnabled true
testProguardFiles getDefaultProguardFile('proguard-android.txt'), 'test-proguard-rules.pro'
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
buildFeatures {
viewBinding = true
}
}
dependencies {
implementation project(":internal:lintchecks")
implementation project(":internal:chooserx")
implementation 'com.google.android.material:material:1.6.1'
implementation "androidx.activity:activity-ktx:1.6.0"
// Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom)
implementation platform('com.google.firebase:firebase-bom:30.5.0')
// Firebase Crashlytics (Kotlin)
implementation 'com.google.firebase:firebase-crashlytics-ktx'
// For an optimal experience using Crashlytics, add the Firebase SDK
// for Google Analytics. This is recommended, but not required.
implementation 'com.google.firebase:firebase-analytics'
// For use in the CustomKeySamples -- for testing Google Api Availability.
implementation 'com.google.android.gms:play-services-base:18.1.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation 'androidx.test:rules:1.4.0'
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
}
Step 2. Create Firebase App
You will need to create or setup a Firebase app first. This link explains how to do so.
Step 3. Our Android Manifest
We will need to look at our AndroidManifest.xml
.
(a). AndroidManifest.xml
Our
AndroidManifest
file.
Here we will add the following permission:
- Our
ACCESS_NETWORK_STATE
permission.
Here is the full Android Manifest file:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.samples.quickstart.crash">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".EntryChoiceActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".java.MainActivity" />
<activity android:name=".kotlin.MainActivity" />
</application>
</manifest>
Step 4. Design Layouts
(a). activity_main.xml
Our
activity_main
layout.
Inside your /res/layout/
directory create an xml layout file named activity_main.xml
and add the following widgets:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.google.samples.quickstart.crash.java.MainActivity">
<ImageView
android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/firebase_lockup_400"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/crashButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/crash_button_label"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toEndOf="@+id/icon"
app:layout_constraintStart_toStartOf="@+id/icon"
app:layout_constraintTop_toBottomOf="@+id/icon" />
<CheckBox
android:id="@+id/catchCrashCheckBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/catch_crash_checkbox_label"
android:layout_marginTop="10dp"
app:layout_constraintEnd_toEndOf="@+id/crashButton"
app:layout_constraintStart_toStartOf="@+id/crashButton"
app:layout_constraintTop_toBottomOf="@+id/crashButton" />
</androidx.constraintlayout.widget.ConstraintLayout>
Step 5. Write Code
Finally we need to write our code as follows:
(a). EntryChoiceActivity.kt
Our
EntryChoiceActivity
class.
Create a Kotlin file named EntryChoiceActivity.kt
and create a class that derives from BaseEntryChoiceActivity
and add its contents as follows:
We will be overriding the following functions:
getChoices(): List<Choice>
.
Here is the full code:
package replace_with_your_package_name
import android.content.Intent
import com.firebase.example.internal.BaseEntryChoiceActivity
import com.firebase.example.internal.Choice
import com.google.samples.quickstart.crash.java.MainActivity
class EntryChoiceActivity : BaseEntryChoiceActivity() {
override fun getChoices(): List<Choice> {
return listOf(
Choice(
"Java",
"Run the Firebase Crash quickstart written in Java.",
Intent(this, MainActivity::class.java)),
Choice(
"Kotlin",
"Run the Firebase Crash quickstart written in Kotlin.",
Intent(this, com.google.samples.quickstart.crash.kotlin.MainActivity::class.java))
)
}
}
(b). CustomKeySamples.kt
Our
CustomKeySamples
class.
Create a Kotlin file named CustomKeySamples.kt
.
We will then add imports from android SDK and other packages. Here are some of the imports we will use in this class:
Manifest
from theandroid
package.SuppressLint
from theandroid.annotation
package.Context
from theandroid.content
package.PackageManager
from theandroid.content.pm
package.ConnectivityManager
from theandroid.net
package.NetworkCallback
from theandroid.net.ConnectivityManager
package.Network
from theandroid.net
package.NetworkCapabilities
from theandroid.net
package.Build
from theandroid.os
package.TelephonyManager
from theandroid.telephony
package.View
from theandroid.view
package.ContextCompat
from theandroidx.core.content
package.synchronized
from thekotlinx.coroutines.internal
package.
We will be overriding the following functions:
onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities)
.
We will be creating the following methods:
setSampleCustomKeys()
.updateAndTrackNetworkState()
.stopTrackingNetworkState()
.updateNetworkCapabilityCustomKeys(parameter)
- Our function will take aNetworkCapabilities
object as a parameter.addPhoneStateRequiredNetworkKeys()
.focusListener(view: View, hasFocus: Boolean)
.
(a). Our updateAndTrackNetworkState()
function
Write the updateAndTrackNetworkState()
function as follows:
fun updateAndTrackNetworkState() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
connectivityManager
.getNetworkCapabilities(connectivityManager.activeNetwork)
?.let { updateNetworkCapabilityCustomKeys(it) }
kotlin.synchronized(this) {
if (callback == null) {
// Set up a callback to match our best-practices around custom keys being up-to-date
val newCallback: NetworkCallback = object : NetworkCallback() {
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
updateNetworkCapabilityCustomKeys(networkCapabilities)
}
}
callback = newCallback
connectivityManager.registerDefaultNetworkCallback(newCallback)
}
}
}
}
(b). Our focusListener()
function
Write the focusListener()
function as follows:
fun focusListener(view: View, hasFocus: Boolean) {
if (hasFocus) {
Firebase.crashlytics.setCustomKey("view_focus", view.id)
}
}
(c). Our updateNetworkCapabilityCustomKeys()
function
Write the updateNetworkCapabilityCustomKeys()
function as follows:
private fun updateNetworkCapabilityCustomKeys(networkCapabilities: NetworkCapabilities) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Firebase.crashlytics.setCustomKeys {
key("Network Bandwidth", networkCapabilities.linkDownstreamBandwidthKbps)
key("Network Upstream", networkCapabilities.linkUpstreamBandwidthKbps)
key("Network Metered", networkCapabilities
.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED))
// This key is long and not as easy to filter by.
key("Network Capabilities", networkCapabilities.toString())
}
}
}
(d). Our stopTrackingNetworkState()
function
Write the stopTrackingNetworkState()
function as follows:
fun stopTrackingNetworkState() {
val connectivityManager = context
.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val oldCallback = callback
kotlin.synchronized(this) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && oldCallback != null) {
connectivityManager.unregisterNetworkCallback(oldCallback)
callback = null
}
}
}
(e). Our setSampleCustomKeys()
function
Write the setSampleCustomKeys()
function as follows:
fun setSampleCustomKeys() {
Firebase.crashlytics.setCustomKeys {
key("Locale", locale)
key("Screen Density", density)
key("Google Play Services Availability", googlePlayServicesAvailability)
key("Os Version", osVersion)
key("Install Source", installSource)
key("Preferred ABI", preferredAbi)
}
}
(f). Our addPhoneStateRequiredNetworkKeys()
function
Write the addPhoneStateRequiredNetworkKeys()
function as follows:
fun addPhoneStateRequiredNetworkKeys() {
val telephonyManager = context
.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE)
!= PackageManager.PERMISSION_GRANTED) {
return
}
val networkType: Int = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
telephonyManager.dataNetworkType
} else {
telephonyManager.networkType
}
Firebase.crashlytics.setCustomKeys {
key("Network Type", networkType)
key("Sim Operator", telephonyManager.simOperator)
}
}
Here is the full code:
package replace_with_your_package_name
import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import android.net.ConnectivityManager
import android.net.ConnectivityManager.NetworkCallback
import android.net.Network
import android.net.NetworkCapabilities
import android.os.Build
import android.telephony.TelephonyManager
import android.view.View
import androidx.core.content.ContextCompat
import com.google.android.gms.common.GoogleApiAvailability
import com.google.firebase.crashlytics.ktx.crashlytics
import com.google.firebase.crashlytics.ktx.setCustomKeys
import com.google.firebase.ktx.Firebase
import kotlinx.coroutines.internal.synchronized
/**
* The following are samples of custom keys that may be useful to record via Crashlytics prior to a Crash.
*
* These utility methods are not meant to be comprehensive, but they are illustrative of the types of things you
* could track using custom keys in Crashlytics.
*/
class CustomKeySamples(private val context: Context, private var callback: NetworkCallback? = null) {
/**
* Set a subset of custom keys simultaneously.
*/
fun setSampleCustomKeys() {
Firebase.crashlytics.setCustomKeys {
key("Locale", locale)
key("Screen Density", density)
key("Google Play Services Availability", googlePlayServicesAvailability)
key("Os Version", osVersion)
key("Install Source", installSource)
key("Preferred ABI", preferredAbi)
}
}
/**
* Update network state and add a hook to update network state going forward.
*
* Note: This code is executed above API level N.
*/
fun updateAndTrackNetworkState() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
connectivityManager
.getNetworkCapabilities(connectivityManager.activeNetwork)
?.let { updateNetworkCapabilityCustomKeys(it) }
kotlin.synchronized(this) {
if (callback == null) {
// Set up a callback to match our best-practices around custom keys being up-to-date
val newCallback: NetworkCallback = object : NetworkCallback() {
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
updateNetworkCapabilityCustomKeys(networkCapabilities)
}
}
callback = newCallback
connectivityManager.registerDefaultNetworkCallback(newCallback)
}
}
}
}
/**
* Remove the handler for the network state.
*
* Note: This code is executed above API level N.
*/
fun stopTrackingNetworkState() {
val connectivityManager = context
.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val oldCallback = callback
kotlin.synchronized(this) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && oldCallback != null) {
connectivityManager.unregisterNetworkCallback(oldCallback)
callback = null
}
}
}
private fun updateNetworkCapabilityCustomKeys(networkCapabilities: NetworkCapabilities) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Firebase.crashlytics.setCustomKeys {
key("Network Bandwidth", networkCapabilities.linkDownstreamBandwidthKbps)
key("Network Upstream", networkCapabilities.linkUpstreamBandwidthKbps)
key("Network Metered", networkCapabilities
.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED))
// This key is long and not as easy to filter by.
key("Network Capabilities", networkCapabilities.toString())
}
}
}
/**
* @see {@link com.google.samples.quickstart.crash.java.CustomKeySamples.updateAndTrackNetworkState}, which does not require READ_PHONE_STATE
* and returns more useful information about bandwidth, metering, and capabilities.
*
* Supressed deprecation warning because that code path is only used below API Level N.
*
* Supressed Lint warning because READ_PHONE_STATE is a high priority permission and
* we don't want to enforce needing it for this code example.
*/
@Suppress("DEPRECATION")
@Deprecated("Prefer updateAndTrackNetworkState, which does not require READ_PHONE_STATE")
@SuppressLint("MissingPermission")
fun addPhoneStateRequiredNetworkKeys() {
val telephonyManager = context
.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE)
!= PackageManager.PERMISSION_GRANTED) {
return
}
val networkType: Int = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
telephonyManager.dataNetworkType
} else {
telephonyManager.networkType
}
Firebase.crashlytics.setCustomKeys {
key("Network Type", networkType)
key("Sim Operator", telephonyManager.simOperator)
}
}
/**
* Retrieve the locale information for the app.
*
* Supressed deprecation warning because that code path is only used below API Level N.
*/
@Suppress("DEPRECATION")
val locale: String
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
context
.resources
.configuration
.locales[0].toString()
} else {
context
.resources
.configuration.locale.toString()
}
/**
* Retrieve the screen density information for the app.
*/
val density: Float
get() = context
.resources
.displayMetrics.density
/**
* Retrieve the locale information for the app.
*/
val googlePlayServicesAvailability: String
get() = if (GoogleApiAvailability
.getInstance()
.isGooglePlayServicesAvailable(context) == 0) "Unavailable" else "Available"
/**
* Return the underlying kernel version of the Android device.
*/
val osVersion: String
get() = System.getProperty("os.version") ?: "Unknown"
/**
* Retrieve the preferred ABI of the device. Some devices can support
* multiple ABIs and the first one returned in the preferred one.
*
* Supressed deprecation warning because that code path is only used below Lollipop.
*/
@Suppress("DEPRECATION")
val preferredAbi: String
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Build.SUPPORTED_ABIS[0]
} else Build.CPU_ABI
/**
* Retrieve the install source and return it as a string.
*
* Supressed deprecation warning because that code path is only used below API level R.
*/
@Suppress("DEPRECATION")
val installSource: String
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
try {
val info = context
.packageManager
.getInstallSourceInfo(context.packageName)
// This returns all three of the install source, originating source, and initiating
// source.
"Originating: ${info.originatingPackageName ?: "None"}, " +
"Installing: ${info.installingPackageName ?: "None"}, " +
"Initiating: ${info.initiatingPackageName ?: "None"}"
} catch (e: PackageManager.NameNotFoundException) {
"Unknown"
}
} else {
context.packageManager.getInstallerPackageName(context.packageName) ?: "None"
}
/**
* Add a focus listener that updates a custom key when this view gains the focus.
*/
fun focusListener(view: View, hasFocus: Boolean) {
if (hasFocus) {
Firebase.crashlytics.setCustomKey("view_focus", view.id)
}
}
}
(c). MainActivity.kt
Our
MainActivity
class.
Create a Kotlin file named MainActivity.kt
.
We will then add imports from android SDK and other packages. Here are some of the imports we will use in this class:
Bundle
from theandroid.os
package.AppCompatActivity
from theandroidx.appcompat.app
package.
Next create a class that derives from AppCompatActivity
and add its contents as follows:
We will be overriding the following functions:
onCreate(savedInstanceState: Bundle?)
.onDestroy()
.
Here is the full code:
package replace_with_your_package_name
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.google.firebase.crashlytics.ktx.crashlytics
import com.google.firebase.crashlytics.ktx.setCustomKeys
import com.google.firebase.ktx.Firebase
import com.google.samples.quickstart.crash.databinding.ActivityMainBinding
/**
* This Activity shows the different ways of reporting application crashes.
* - Report non-fatal exceptions that are caught by your app.
* - Automatically Report uncaught crashes.
*
* It also shows how to add log messages to crash reports using log().
*
* Check https://console.firebase.google.com to view and analyze your crash reports.
*
* Check https://firebase.google.com/docs/crashlytics for more information on Firebase Crashlytics.
*/
class MainActivity : AppCompatActivity() {
private lateinit var crashlytics: FirebaseCrashlytics
private lateinit var customKeySamples: CustomKeySamples
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
customKeySamples = CustomKeySamples(applicationContext)
customKeySamples.setSampleCustomKeys()
customKeySamples.updateAndTrackNetworkState()
crashlytics = Firebase.crashlytics
// Log the onCreate event, this will also be printed in logcat
crashlytics.log("onCreate")
// Add some custom values and identifiers to be included in crash reports
crashlytics.setCustomKeys {
key("MeaningOfLife", 42)
key("LastUIAction", "Test value")
}
crashlytics.setUserId("123456789")
// Report a non-fatal exception, for demonstration purposes
crashlytics.recordException(Exception("Non-fatal exception: something went wrong!"))
// Button that causes NullPointerException to be thrown.
binding.crashButton.setOnClickListener {
// Log that crash button was clicked.
crashlytics.log("Crash button clicked.")
// If catchCrashCheckBox is checked catch the exception and report it using
// logException(), Otherwise throw the exception and let Crashlytics automatically
// report the crash.
if (binding.catchCrashCheckBox.isChecked) {
try {
throw NullPointerException()
} catch (ex: NullPointerException) {
// [START crashlytics_log_and_report]
crashlytics.log("NPE caught!")
crashlytics.recordException(ex)
// [END crashlytics_log_and_report]
}
} else {
throw NullPointerException()
}
}
// Log that the Activity was created.
// [START crashlytics_log_event]
crashlytics.log("Activity created")
// [END crashlytics_log_event]
}
override fun onDestroy() {
super.onDestroy()
customKeySamples.stopTrackingNetworkState()
}
companion object {
private const val TAG = "MainActivity"
}
}
Reference
Below are the reference links:
No. | Link |
---|---|
2. | Read more here. |
3. | Follow code author here. |