From 8696ade61282e1b5a5dc70063e0d020c7da8d4e7 Mon Sep 17 00:00:00 2001 From: MichaelVerdon Date: Thu, 4 Dec 2025 14:57:39 +0000 Subject: [PATCH 1/4] feat(crashlytics): add firebase.json support for NDK crash reporting --- packages/crashlytics/android/build.gradle | 17 +++++++++++++++++ .../android/src/main/AndroidManifest.xml | 14 +++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/packages/crashlytics/android/build.gradle b/packages/crashlytics/android/build.gradle index dfb9b21e96..2450eecac9 100644 --- a/packages/crashlytics/android/build.gradle +++ b/packages/crashlytics/android/build.gradle @@ -57,6 +57,20 @@ project.ext { ]) } +apply from: file('./../../app/android/firebase-json.gradle') + +// Default to 'false' for the feature due to memory usage concerns +String crashlyticsNdkEnabled = 'false' + +if (rootProject.ext && rootProject.ext.firebaseJson) { + def rnfbConfig = rootProject.ext.firebaseJson + + // Allow users to opt-in to NDK crash reporting (or whatever feature) + if (rnfbConfig.isFlagEnabled('crashlytics_ndk_enabled', false) == true) { + crashlyticsNdkEnabled = 'true' + } +} + android { def agpVersion = Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')[0].toInteger() if (agpVersion >= 7) { @@ -65,6 +79,9 @@ android { defaultConfig { multiDexEnabled = true + manifestPlaceholders = [ + firebaseJsonCrashlyticsNdkEnabled: crashlyticsNdkEnabled + ] } buildFeatures { diff --git a/packages/crashlytics/android/src/main/AndroidManifest.xml b/packages/crashlytics/android/src/main/AndroidManifest.xml index 3c29ac3d99..f365e17f08 100644 --- a/packages/crashlytics/android/src/main/AndroidManifest.xml +++ b/packages/crashlytics/android/src/main/AndroidManifest.xml @@ -6,10 +6,22 @@ + + + + + + android:initOrder="98" /> From ef52c5a4d36711546bf4b35908e864372e75666d Mon Sep 17 00:00:00 2001 From: MichaelVerdon Date: Thu, 4 Dec 2025 15:04:33 +0000 Subject: [PATCH 2/4] chore: bring back comment --- packages/crashlytics/android/src/main/AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/crashlytics/android/src/main/AndroidManifest.xml b/packages/crashlytics/android/src/main/AndroidManifest.xml index f365e17f08..89e433e9bf 100644 --- a/packages/crashlytics/android/src/main/AndroidManifest.xml +++ b/packages/crashlytics/android/src/main/AndroidManifest.xml @@ -22,6 +22,6 @@ android:name="io.invertase.firebase.crashlytics.ReactNativeFirebaseCrashlyticsInitProvider" android:authorities="${applicationId}.reactnativefirebasecrashlyticsinitprovider" android:exported="false" - android:initOrder="98" /> + android:initOrder="98" /> From fe14a8d0505f0d10803b708c185dc214c96ac002 Mon Sep 17 00:00:00 2001 From: MichaelVerdon Date: Mon, 8 Dec 2025 14:25:01 +0000 Subject: [PATCH 3/4] feat: gwp and ndk setup --- docs/crashlytics/android-setup.md | 45 +++++++++-- docs/crashlytics/usage/index.md | 34 +++++++- packages/crashlytics/android/build.gradle | 81 ++++++++++++++++++- .../android/src/main/AndroidManifest.xml | 11 +++ tests/firebase.json | 1 + 5 files changed, 160 insertions(+), 12 deletions(-) diff --git a/docs/crashlytics/android-setup.md b/docs/crashlytics/android-setup.md index 2ea502c11b..b5a7dbcde0 100644 --- a/docs/crashlytics/android-setup.md +++ b/docs/crashlytics/android-setup.md @@ -57,12 +57,30 @@ apply plugin: 'com.google.firebase.crashlytics' // .. ``` -## 4. (Optional) Enable Crashlytics NDK reporting +## 4. (Optional) Configure Crashlytics NDK and GWP-ASan support + +### Enable NDK Crash Reporting Crashlytics NDK reporting allows you to capture Native Development Kit crashes, e.g. in React Native this will capture crashes originating from the Yoga layout engine. -Add the `firebaseCrashlytics` block line to the `android/app/build.gradle` file: +To enable NDK crash reporting, add the following to your `firebase.json` file: + +```json +{ + "react-native": { + "crashlytics_ndk_enabled": true + } +} +``` + +When `crashlytics_ndk_enabled` is set to `true`, React Native Firebase will automatically: +- Enable NDK crash reporting in the manifest +- Configure automatic native symbol upload for all release build variants + +> **Note**: The automatic symbol upload configuration works with standard release builds and custom build flavors (e.g., `playRelease`, `premiumRelease`). Any build variant with "release" in its name will have symbol upload enabled. + +If you need to manually configure symbol upload or override the automatic configuration, you can add the `firebaseCrashlytics` block to your `android/app/build.gradle` file: ```groovy android { @@ -70,10 +88,6 @@ android { buildTypes { release { - /* Add the firebaseCrashlytics extension (by default, - * it's disabled to improve build speeds) and set - * nativeSymbolUploadEnabled to true along with a pointer to native libs. */ - firebaseCrashlytics { nativeSymbolUploadEnabled true unstrippedNativeLibsDir 'build/intermediates/merged_native_libs/release/out/lib' @@ -84,6 +98,25 @@ android { } ``` +### Configure GWP-ASan Mode + +[GWP-ASan](https://developer.android.com/ndk/guides/gwp-asan) (GWP-AddressSanitizer) is a native memory allocator feature that helps detect heap memory errors. You can configure its behavior using `firebase.json`: + +```json +{ + "react-native": { + "crashlytics_gwp_asan_mode": "default" + } +} +``` + +Available values: +- `"default"` - The default behavior (system-determined, typically enabled for a small percentage of devices) +- `"never"` - Disable GWP-ASan completely +- `"always"` - Enable GWP-ASan on all devices (useful for testing, but not recommended for production due to performance impact) + +> **Recommended**: Use `"default"` for production builds to get memory error detection with minimal performance impact. + ## 5. Rebuild the project Once the above steps have been completed, rebuild your Android project: diff --git a/docs/crashlytics/usage/index.md b/docs/crashlytics/usage/index.md index c4f93149c7..6bdc446b4a 100644 --- a/docs/crashlytics/usage/index.md +++ b/docs/crashlytics/usage/index.md @@ -213,21 +213,47 @@ Because you have stack traces readily available while you're debugging your app, ## Crashlytics NDK React Native Firebase supports [Crashlytics NDK](https://firebase.google.com/docs/crashlytics/ndk-reports) reporting -which is enabled by default but will require a change as described in that link to enable symbol upload. +which allows Crashlytics to capture crashes originating from the Yoga layout engine used by React Native. -This allows Crashlytics to capture crashes originating from the Yoga layout engine used by React Native. +**Note:** NDK reporting is disabled by default due to memory usage concerns. -You can disable Crashlytics NDK in your `firebase.json` config. +### Enable NDK Reporting + +To enable NDK crash reporting, add the following to your `firebase.json`: + +```json +// /firebase.json +{ + "react-native": { + "crashlytics_ndk_enabled": true + } +} +``` + +When enabled, React Native Firebase will automatically: +- Enable NDK crash reporting in your Android app +- Configure automatic native symbol upload for all release build variants + +For more details on Android setup, see the [Android Setup](/crashlytics/android-setup) documentation. + +### Configure GWP-ASan + +[GWP-ASan](https://developer.android.com/ndk/guides/gwp-asan) (GWP-AddressSanitizer) is a native memory allocator feature that helps detect heap memory errors. You can configure its behavior: ```json // /firebase.json { "react-native": { - "crashlytics_ndk_enabled": false + "crashlytics_gwp_asan_mode": "default" } } ``` +Available values: +- `"default"` - System-determined (recommended for production) +- `"never"` - Disable GWP-ASan completely +- `"always"` - Enable on all devices (for testing only) + ## Crashlytics Javascript stacktrace issue generation React Native Crashlytics module by default installs a global javascript exception handler, and it records a crash with a javascript stack trace any time an unhandled javascript exception is thrown. Sometimes it is not desirable behavior since it might duplicate issues in combination with the default mode of javascript global exception handler chaining. We recommend leaving JS crashes enabled and turning off exception handler chaining. However, if you have special crash handling requirements, you may disable this behavior by setting the appropriate option to false: diff --git a/packages/crashlytics/android/build.gradle b/packages/crashlytics/android/build.gradle index 2450eecac9..fb43f00d04 100644 --- a/packages/crashlytics/android/build.gradle +++ b/packages/crashlytics/android/build.gradle @@ -61,14 +61,19 @@ apply from: file('./../../app/android/firebase-json.gradle') // Default to 'false' for the feature due to memory usage concerns String crashlyticsNdkEnabled = 'false' +// Default to 'default' for GWP-ASan mode (other options: 'never', 'always') +String crashlyticsGwpAsanMode = 'default' if (rootProject.ext && rootProject.ext.firebaseJson) { def rnfbConfig = rootProject.ext.firebaseJson - // Allow users to opt-in to NDK crash reporting (or whatever feature) + // Allow users to opt-in to NDK crash reporting if (rnfbConfig.isFlagEnabled('crashlytics_ndk_enabled', false) == true) { crashlyticsNdkEnabled = 'true' } + + // Allow users to configure GWP-ASan mode: 'default', 'never', or 'always' + crashlyticsGwpAsanMode = rnfbConfig.getStringValue('crashlytics_gwp_asan_mode', 'default') } android { @@ -80,7 +85,8 @@ android { defaultConfig { multiDexEnabled = true manifestPlaceholders = [ - firebaseJsonCrashlyticsNdkEnabled: crashlyticsNdkEnabled + firebaseJsonCrashlyticsNdkEnabled: crashlyticsNdkEnabled, + firebaseJsonCrashlyticsGwpAsanMode: crashlyticsGwpAsanMode ] } @@ -117,3 +123,74 @@ ReactNative.shared.applyPackageVersion() ReactNative.shared.applyDefaultExcludes() ReactNative.module.applyAndroidVersions() ReactNative.module.applyReactNativeDependency("api") + +// Configure automatic NDK symbol upload for release variants if NDK is enabled +if (rootProject.ext && rootProject.ext.firebaseJson) { + def rnfbConfig = rootProject.ext.firebaseJson + + if (rnfbConfig.isFlagEnabled('crashlytics_ndk_enabled', false) == true) { + rootProject.afterEvaluate { + // Find the app project (the one with com.android.application plugin) + def appProject = rootProject.subprojects.find { + it.plugins.hasPlugin('com.android.application') + } + + if (appProject != null) { + appProject.afterEvaluate { + try { + def android = appProject.extensions.findByName('android') + + if (android != null) { + // Try to configure all release-type variants + try { + android.applicationVariants.all { variant -> + // Match variants with 'release' in the name (case insensitive) + if (variant.name.toLowerCase().contains('release')) { + try { + // Access or create the firebaseCrashlytics extension for this variant + def buildType = android.buildTypes.findByName(variant.buildType.name) + if (buildType != null) { + buildType.configure { + // Check if firebaseCrashlytics extension exists + try { + firebaseCrashlytics { + nativeSymbolUploadEnabled = true + } + appProject.logger.info("RNFirebase: Enabled Crashlytics NDK symbol upload for build type '${variant.buildType.name}'") + } catch (Exception e) { + // Extension might not exist if Crashlytics plugin not applied + appProject.logger.warn("RNFirebase: Could not configure Crashlytics NDK symbol upload for build type '${variant.buildType.name}'. " + + "Ensure the Crashlytics Gradle plugin is applied in your app/build.gradle: ${e.message}") + } + } + } + } catch (Exception e) { + appProject.logger.warn("RNFirebase: Could not configure variant '${variant.name}': ${e.message}") + } + } + } + } catch (Exception e) { + // Fallback: just try to configure the 'release' build type directly + appProject.logger.info("RNFirebase: Using fallback approach for NDK symbol upload configuration") + try { + android.buildTypes.release { + firebaseCrashlytics { + nativeSymbolUploadEnabled = true + } + } + appProject.logger.info("RNFirebase: Enabled Crashlytics NDK symbol upload for 'release' build type") + } catch (Exception fallbackError) { + appProject.logger.warn("RNFirebase: Could not automatically enable Crashlytics NDK symbol upload. " + + "You may need to configure it manually in your app/build.gradle. " + + "See: https://firebase.google.com/docs/crashlytics/android/get-started-ndk#set-up-automatic-native-symbols-upload") + } + } + } + } catch (Exception e) { + appProject.logger.warn("RNFirebase: Failed to configure automatic Crashlytics NDK symbol upload: ${e.message}") + } + } + } + } + } +} diff --git a/packages/crashlytics/android/src/main/AndroidManifest.xml b/packages/crashlytics/android/src/main/AndroidManifest.xml index 89e433e9bf..9bf5a280cf 100644 --- a/packages/crashlytics/android/src/main/AndroidManifest.xml +++ b/packages/crashlytics/android/src/main/AndroidManifest.xml @@ -18,6 +18,17 @@ android:name="firebase_crashlytics_ndk_enabled" android:value="${firebaseJsonCrashlyticsNdkEnabled}" /> + + + + Date: Tue, 9 Dec 2025 12:33:26 +0000 Subject: [PATCH 4/4] chore: logging --- packages/crashlytics/android/build.gradle | 82 ++++++++--------------- 1 file changed, 27 insertions(+), 55 deletions(-) diff --git a/packages/crashlytics/android/build.gradle b/packages/crashlytics/android/build.gradle index fb43f00d04..c0096fa865 100644 --- a/packages/crashlytics/android/build.gradle +++ b/packages/crashlytics/android/build.gradle @@ -125,70 +125,42 @@ ReactNative.module.applyAndroidVersions() ReactNative.module.applyReactNativeDependency("api") // Configure automatic NDK symbol upload for release variants if NDK is enabled -if (rootProject.ext && rootProject.ext.firebaseJson) { - def rnfbConfig = rootProject.ext.firebaseJson - - if (rnfbConfig.isFlagEnabled('crashlytics_ndk_enabled', false) == true) { - rootProject.afterEvaluate { +// This runs after all projects are evaluated to allow proper configuration +gradle.projectsEvaluated { + if (rootProject.ext && rootProject.ext.firebaseJson) { + def rnfbConfig = rootProject.ext.firebaseJson + + if (rnfbConfig.isFlagEnabled('crashlytics_ndk_enabled', false) == true) { // Find the app project (the one with com.android.application plugin) - def appProject = rootProject.subprojects.find { + def targetAppProject = rootProject.subprojects.find { it.plugins.hasPlugin('com.android.application') } - if (appProject != null) { - appProject.afterEvaluate { - try { - def android = appProject.extensions.findByName('android') - - if (android != null) { - // Try to configure all release-type variants - try { - android.applicationVariants.all { variant -> - // Match variants with 'release' in the name (case insensitive) - if (variant.name.toLowerCase().contains('release')) { - try { - // Access or create the firebaseCrashlytics extension for this variant - def buildType = android.buildTypes.findByName(variant.buildType.name) - if (buildType != null) { - buildType.configure { - // Check if firebaseCrashlytics extension exists - try { - firebaseCrashlytics { - nativeSymbolUploadEnabled = true - } - appProject.logger.info("RNFirebase: Enabled Crashlytics NDK symbol upload for build type '${variant.buildType.name}'") - } catch (Exception e) { - // Extension might not exist if Crashlytics plugin not applied - appProject.logger.warn("RNFirebase: Could not configure Crashlytics NDK symbol upload for build type '${variant.buildType.name}'. " + - "Ensure the Crashlytics Gradle plugin is applied in your app/build.gradle: ${e.message}") - } - } - } - } catch (Exception e) { - appProject.logger.warn("RNFirebase: Could not configure variant '${variant.name}': ${e.message}") - } - } - } - } catch (Exception e) { - // Fallback: just try to configure the 'release' build type directly - appProject.logger.info("RNFirebase: Using fallback approach for NDK symbol upload configuration") + if (targetAppProject != null) { + try { + def android = targetAppProject.extensions.findByName('android') + + if (android != null && android.hasProperty('buildTypes')) { + // Configure all build types that contain 'release' (case insensitive) + android.buildTypes.all { buildType -> + if (buildType.name.toLowerCase().contains('release')) { try { - android.buildTypes.release { - firebaseCrashlytics { - nativeSymbolUploadEnabled = true - } - } - appProject.logger.info("RNFirebase: Enabled Crashlytics NDK symbol upload for 'release' build type") - } catch (Exception fallbackError) { - appProject.logger.warn("RNFirebase: Could not automatically enable Crashlytics NDK symbol upload. " + - "You may need to configure it manually in your app/build.gradle. " + - "See: https://firebase.google.com/docs/crashlytics/android/get-started-ndk#set-up-automatic-native-symbols-upload") + buildType.ext.set('firebaseCrashlytics', [:]) + buildType.firebaseCrashlytics = [ + nativeSymbolUploadEnabled: true + ] + targetAppProject.logger.info("RNFirebase: Enabled Crashlytics NDK symbol upload for build type '${buildType.name}'") + } catch (Exception e) { + targetAppProject.logger.warn("RNFirebase: Could not automatically configure Crashlytics NDK symbol upload for build type '${buildType.name}'. " + + "You may need to add it manually to your app/build.gradle. See: https://rnfirebase.io/crashlytics/android-setup") } } } - } catch (Exception e) { - appProject.logger.warn("RNFirebase: Failed to configure automatic Crashlytics NDK symbol upload: ${e.message}") } + } catch (Exception e) { + targetAppProject.logger.warn("RNFirebase: Could not automatically enable Crashlytics NDK symbol upload: ${e.message}") + targetAppProject.logger.info("Please add the following to your android/app/build.gradle:") + targetAppProject.logger.info(" android { buildTypes { release { firebaseCrashlytics { nativeSymbolUploadEnabled true } } } }") } } }