react-native-firebase icon indicating copy to clipboard operation
react-native-firebase copied to clipboard

[🐛] [Android] OOM Crash due to excessive notification storage in SharedPreferences

Open sean5940 opened this issue 3 months ago • 2 comments

Issue

🔥 [Android] OOM Crash due to excessive notification storage in SharedPreferences (ReactNativeFirebaseMessagingStoreImpl)

We are encountering java.lang.OutOfMemoryError on Android devices. The root cause is that ReactNativeFirebaseMessagingStoreImpl stores up to 100 notifications in SharedPreferences by default. When the app receives many notifications with large payloads, the SharedPreferences file becomes too large. Since SharedPreferences loads the entire file into memory, this leads to OOM crashes, especially on devices with limited heap memory.

Stack Trace:

java.lang.OutOfMemoryError: Failed to allocate a 24 byte allocation with 425280 free bytes and 415KB until OOM...
    at java.util.HashMap.newNode(HashMap.java:1907)
    at java.util.HashMap.putVal(HashMap.java:636)
    at java.util.HashMap.putMapEntries(HashMap.java:521)
    at java.util.HashMap.<init>(HashMap.java:491)
    at android.app.SharedPreferencesImpl$EditorImpl.commitToMemory(SharedPreferencesImpl.java:539)
    at android.app.SharedPreferencesImpl$EditorImpl.apply(SharedPreferencesImpl.java:486)
    at io.invertase.firebase.common.UniversalFirebasePreferences.setStringValue(UniversalFirebasePreferences.java:66)
    at io.invertase.firebase.messaging.ReactNativeFirebaseMessagingStoreImpl.storeFirebaseMessage(ReactNativeFirebaseMessagingStoreImpl.java:43)

Analysis: The ReactNativeFirebaseMessagingStoreImpl.java has a constant MAX_SIZE_NOTIFICATIONS = 100. Every time a notification is received (whether in foreground or background), it is stored in SharedPreferences. If the notification data is large, storing 100 entries can easily exceed the available heap size during the SharedPreferences load/commit process.

Proposed Solution & Fix: We have successfully patched the library locally with the following changes:

  1. Reduce the default limit: 100 seems excessive. We reduced it to 20.
  2. Make it configurable: Added support for rn_firebase_messaging_max_stored_notifications in AndroidManifest.xml.
  3. Safety Cap: Even if configured higher, we capped the limit at 100 to prevent OOM.
  4. Improve cleanup logic: Changed if to while loop to aggressively clean up excess notifications.

Example Patch Code:

// In ReactNativeFirebaseMessagingStoreImpl.java

private static int MAX_SIZE_NOTIFICATIONS = 20; // Default reduced to 20
private static boolean isInitialized = false;

private int getMaxNotificationSize() {
    if (isInitialized) return MAX_SIZE_NOTIFICATIONS;
    try {
        Context context = ReactNativeFirebaseApp.getApplicationContext();
        ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
        if (appInfo.metaData != null) {
            int configValue = appInfo.metaData.getInt("rn_firebase_messaging_max_stored_notifications", 20);
            // Safety cap: never exceed 100 to prevent OOM
            MAX_SIZE_NOTIFICATIONS = Math.min(configValue, 100);
        }
    } catch (Exception e) {
        // Ignore
    }
    isInitialized = true;
    return MAX_SIZE_NOTIFICATIONS;
}

@Override
public void storeFirebaseMessage(RemoteMessage remoteMessage) {
    // ...
    // check and remove old notifications message
    List<String> allNotificationList = convertToArray(notifications);

    // Changed 'if' to 'while' for aggressive cleanup
    while (allNotificationList.size() > getMaxNotificationSize()) {
        String firstRemoteMessageId = allNotificationList.get(0);
        preferences.remove(firstRemoteMessageId);
        notifications = removeRemoteMessage(firstRemoteMessageId, notifications);
        allNotificationList.remove(0);
    }
    // ...
}

Project Files

Javascript

Click To Expand

package.json:

{
  "dependencies": {
    "react-native": "0.81.5",
    "@react-native-firebase/app": "21.7.1",
    "@react-native-firebase/messaging": "21.7.1"
  }
}

firebase.json for react-native-firebase v6:

# N/A

iOS

Click To Expand

ios/Podfile:

  • [ ] I'm not using Pods
  • [x] I'm using Pods and my Podfile looks like:
# N/A

AppDelegate.m:

// N/A

Android

Click To Expand

Have you converted to AndroidX?

  • [x] my application is an AndroidX application?
  • [x] I am using android/gradle.settings jetifier=true for Android compatibility?
  • [ ] I am using the NPM package jetifier for react-native compatibility?

android/build.gradle:

// N/A

android/app/build.gradle:

// N/A

android/settings.gradle:

// N/A

MainApplication.java:

// N/A

AndroidManifest.xml:

<!-- We added this configuration to solve the issue -->
<meta-data
    android:name="rn_firebase_messaging_max_stored_notifications"
    android:value="20" />

Environment

Click To Expand

react-native info output:

System:
    OS: macOS
Binaries:
    Node: 20.x
    Yarn: N/A
    npm: 10.x
    Watchman: Yes
SDKs:
    iOS SDK:
      Platforms: iOS
    Android SDK:
      API Levels: 34
IDEs:
    Android Studio: Yes
    Xcode: Yes
Languages:
    Java: 17
    Python: N/A
npmPackages:
    @react-native-community/cli: Not Found
    react: 18.3.1
    react-native: 0.81.5
  • Platform that you're experiencing the issue on:
    • [ ] iOS
    • [x] Android
    • [ ] iOS but have not tested behavior on Android
    • [ ] Android but have not tested behavior on iOS
    • [ ] Both
  • react-native-firebase version you're using that has this issue:
    • 21.7.1
  • Firebase module(s) you're using that has the issue:
    • Messaging
  • Are you using TypeScript?
    • Y

sean5940 avatar Nov 24 '25 09:11 sean5940

Hi there, checking the native android-sdk, it does not seem to let you configure this and if you cannot configure it there we wouldn't be able to allow it here. We could probably allow only changing the max messages. I will check with the rest of the team 😄 . One of our core focuses here on React Native Firebase is to keep it as consistent with the native sdks as we can. If we cannot accept the change, the android-sdk would be a good place to ask about this and would in result fix it here (which would probably require changing that one variable anyways.)

MichaelVerdon avatar Nov 24 '25 10:11 MichaelVerdon

This PR may sufficiently fix this:

  • #8776

mikehardy avatar Nov 26 '25 02:11 mikehardy