From 41ca8efb3e20177a93dae834fa808209d7a3603b Mon Sep 17 00:00:00 2001 From: Sean Jung Date: Fri, 28 Nov 2025 18:31:11 +0900 Subject: [PATCH 1/5] fix(messaging): honor custom max stored notifications --- .../common/ReactNativeFirebaseMeta.java | 6 +++ .../ReactNativeFirebasePreferences.java | 8 ++++ ...ReactNativeFirebaseMessagingStoreImpl.java | 48 ++++++++++++++++--- 3 files changed, 55 insertions(+), 7 deletions(-) diff --git a/packages/app/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseMeta.java b/packages/app/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseMeta.java index ebbcc8eac8..82c24ebce3 100644 --- a/packages/app/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseMeta.java +++ b/packages/app/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseMeta.java @@ -70,6 +70,12 @@ public String getStringValue(String key, String defaultValue) { return metaData.getString(META_PREFIX + key, defaultValue); } + public int getIntValue(String key, int defaultValue) { + Bundle metaData = getMetaData(); + if (metaData == null) return defaultValue; + return metaData.getInt(META_PREFIX + key, defaultValue); + } + public WritableMap getAll() { Bundle metaData = getMetaData(); WritableMap map = Arguments.createMap(); diff --git a/packages/app/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebasePreferences.java b/packages/app/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebasePreferences.java index c74f935ca8..ff396f7f55 100644 --- a/packages/app/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebasePreferences.java +++ b/packages/app/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebasePreferences.java @@ -46,6 +46,14 @@ public boolean getBooleanValue(String key, boolean defaultValue) { return getPreferences().getBoolean(key, defaultValue); } + public void setIntValue(String key, int value) { + getPreferences().edit().putInt(key,value).apply(); + } + + public int getIntValue(String key, int defaultValue) { + return getPreferences().getInt(key,defaultValue); + } + public void setLongValue(String key, long value) { getPreferences().edit().putLong(key, value).apply(); } diff --git a/packages/messaging/android/src/main/java/io/invertase/firebase/messaging/ReactNativeFirebaseMessagingStoreImpl.java b/packages/messaging/android/src/main/java/io/invertase/firebase/messaging/ReactNativeFirebaseMessagingStoreImpl.java index 03cb8f2eaf..94bec22f68 100644 --- a/packages/messaging/android/src/main/java/io/invertase/firebase/messaging/ReactNativeFirebaseMessagingStoreImpl.java +++ b/packages/messaging/android/src/main/java/io/invertase/firebase/messaging/ReactNativeFirebaseMessagingStoreImpl.java @@ -8,31 +8,65 @@ import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.WritableMap; import com.google.firebase.messaging.RemoteMessage; -import io.invertase.firebase.common.UniversalFirebasePreferences; + +import org.json.JSONException; +import org.json.JSONObject; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import org.json.JSONException; -import org.json.JSONObject; + +import io.invertase.firebase.common.ReactNativeFirebaseJSON; +import io.invertase.firebase.common.ReactNativeFirebaseMeta; +import io.invertase.firebase.common.ReactNativeFirebasePreferences; +import io.invertase.firebase.common.UniversalFirebasePreferences; public class ReactNativeFirebaseMessagingStoreImpl implements ReactNativeFirebaseMessagingStore { + private static final String KEY_MAX_STORED_NOTIFICATIONS = "rn_firebase_messaging_max_stored_notifications"; private static final String S_KEY_ALL_NOTIFICATION_IDS = "all_notification_ids"; - private static final int MAX_SIZE_NOTIFICATIONS = 100; private final String DELIMITER = ","; + private static final int DEFAULT_MAX_SIZE_NOTIFICATIONS = 100; + private static final int maxNotificationSize = resolveMaxNotificationSize(); + + private static int resolveMaxNotificationSize() { + int maxSize = DEFAULT_MAX_SIZE_NOTIFICATIONS; + ReactNativeFirebaseJSON json = ReactNativeFirebaseJSON.getSharedInstance(); + ReactNativeFirebaseMeta meta = ReactNativeFirebaseMeta.getSharedInstance(); + ReactNativeFirebasePreferences prefs = ReactNativeFirebasePreferences.getSharedInstance(); + + try { + // Priority: SharedPreferences -> firebase.json -> AndroidManifest + // Note: ReactNativeFirebaseMeta doesn't have getIntValue, so we check meta.contains + // and read from AndroidManifest directly if needed + if (prefs.contains(KEY_MAX_STORED_NOTIFICATIONS)) { + maxSize = prefs.getIntValue(KEY_MAX_STORED_NOTIFICATIONS, DEFAULT_MAX_SIZE_NOTIFICATIONS); + } else if (json.contains(KEY_MAX_STORED_NOTIFICATIONS)) { + maxSize = json.getIntValue(KEY_MAX_STORED_NOTIFICATIONS, DEFAULT_MAX_SIZE_NOTIFICATIONS); + } else if (meta.contains(KEY_MAX_STORED_NOTIFICATIONS)) { + maxSize = meta.getIntValue(KEY_MAX_STORED_NOTIFICATIONS, DEFAULT_MAX_SIZE_NOTIFICATIONS); + } + + // Safety cap: prevent values > 100 to avoid re-introducing OOM risk + return Math.min(maxSize, DEFAULT_MAX_SIZE_NOTIFICATIONS); + } catch (Exception e) { + // Ignore and use default + return DEFAULT_MAX_SIZE_NOTIFICATIONS; + } + } @Override public void storeFirebaseMessage(RemoteMessage remoteMessage) { try { String remoteMessageString = - reactToJSON(remoteMessageToWritableMap(remoteMessage)).toString(); + reactToJSON(remoteMessageToWritableMap(remoteMessage)).toString(); // Log.d("storeFirebaseMessage", remoteMessageString); UniversalFirebasePreferences preferences = UniversalFirebasePreferences.getSharedInstance(); // remove old notifications message before store to free space as needed String notificationIds = preferences.getStringValue(S_KEY_ALL_NOTIFICATION_IDS, ""); List allNotificationList = convertToArray(notificationIds); - while (allNotificationList.size() > MAX_SIZE_NOTIFICATIONS - 1) { + while (allNotificationList.size() > maxNotificationSize - 1) { clearFirebaseMessage(allNotificationList.get(0)); allNotificationList.remove(0); } @@ -61,7 +95,7 @@ public RemoteMessage getFirebaseMessage(String remoteMessageId) { @Override public WritableMap getFirebaseMessageMap(String remoteMessageId) { String remoteMessageString = - UniversalFirebasePreferences.getSharedInstance().getStringValue(remoteMessageId, null); + UniversalFirebasePreferences.getSharedInstance().getStringValue(remoteMessageId, null); if (remoteMessageString != null) { // Log.d("getFirebaseMessage", remoteMessageString); try { From 56553db6d90e24869e39d5477e78f269274043c8 Mon Sep 17 00:00:00 2001 From: Sean Jung Date: Fri, 28 Nov 2025 18:36:56 +0900 Subject: [PATCH 2/5] fix(messaging): enforce min stored notification limit --- .../messaging/ReactNativeFirebaseMessagingStoreImpl.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/messaging/android/src/main/java/io/invertase/firebase/messaging/ReactNativeFirebaseMessagingStoreImpl.java b/packages/messaging/android/src/main/java/io/invertase/firebase/messaging/ReactNativeFirebaseMessagingStoreImpl.java index 94bec22f68..abef67ca2d 100644 --- a/packages/messaging/android/src/main/java/io/invertase/firebase/messaging/ReactNativeFirebaseMessagingStoreImpl.java +++ b/packages/messaging/android/src/main/java/io/invertase/firebase/messaging/ReactNativeFirebaseMessagingStoreImpl.java @@ -27,6 +27,7 @@ public class ReactNativeFirebaseMessagingStoreImpl implements ReactNativeFirebas private static final String S_KEY_ALL_NOTIFICATION_IDS = "all_notification_ids"; private final String DELIMITER = ","; private static final int DEFAULT_MAX_SIZE_NOTIFICATIONS = 100; + private static final int MIN_SIZE_NOTIFICATIONS = 20; private static final int maxNotificationSize = resolveMaxNotificationSize(); private static int resolveMaxNotificationSize() { @@ -47,8 +48,8 @@ private static int resolveMaxNotificationSize() { maxSize = meta.getIntValue(KEY_MAX_STORED_NOTIFICATIONS, DEFAULT_MAX_SIZE_NOTIFICATIONS); } - // Safety cap: prevent values > 100 to avoid re-introducing OOM risk - return Math.min(maxSize, DEFAULT_MAX_SIZE_NOTIFICATIONS); + // Clamp to allowed range to avoid OOM (upper) or mass deletions (lower) + return Math.max(MIN_SIZE_NOTIFICATIONS, Math.min(maxSize, DEFAULT_MAX_SIZE_NOTIFICATIONS)); } catch (Exception e) { // Ignore and use default return DEFAULT_MAX_SIZE_NOTIFICATIONS; From 0336ac0d5f1930b08755303ddb7f8c47c4965ac6 Mon Sep 17 00:00:00 2001 From: Sean Jung Date: Fri, 28 Nov 2025 18:56:25 +0900 Subject: [PATCH 3/5] feat(messaging): add stored notification config --- packages/app/firebase-schema.json | 6 ++++++ tests/firebase.json | 1 + 2 files changed, 7 insertions(+) diff --git a/packages/app/firebase-schema.json b/packages/app/firebase-schema.json index 3733edd956..421ae0129b 100644 --- a/packages/app/firebase-schema.json +++ b/packages/app/firebase-schema.json @@ -119,6 +119,12 @@ "description": "On iOS, indicating how to present a notification in a foreground app.", "type": "array" }, + "rn_firebase_messaging_max_stored_notifications": { + "description": "Controls how many notifications are retained on-device for background storage. Priority order is SharedPreferences -> firebase.json -> AndroidManifest. Values outside the 20-100 range are clamped to prevent excessive deletions or OOM.", + "type": "number", + "minimum": 20, + "maximum": 100 + }, "perf_auto_collection_enabled": { "description": "Disable or enable auto collection of performance monitoring data collection.\n This is useful for opt-in-first data flows, for example when dealing with GDPR compliance.\nThis can be overridden in JavaScript.", "type": "boolean" diff --git a/tests/firebase.json b/tests/firebase.json index c61567ca73..06b26454bc 100644 --- a/tests/firebase.json +++ b/tests/firebase.json @@ -21,6 +21,7 @@ "messaging_android_notification_color": "@color/hotpink", "messaging_ios_auto_register_for_remote_messages": false, "messaging_android_notification_delegation_enabled": true, + "rn_firebase_messaging_max_stored_notifications": 50, "analytics_auto_collection_enabled": true, "analytics_collection_deactivated": false, From d91c462799751134e4efbe7c1dcb42e724164a40 Mon Sep 17 00:00:00 2001 From: Sean Jung Date: Mon, 8 Dec 2025 14:42:01 +0900 Subject: [PATCH 4/5] fix(messaging, android): update config key name and remove clamping per review - Rename config key from 'rn_firebase_messaging_max_stored_notifications' to 'messaging_max_stored_notifications' for consistency - Remove clamping logic (min/max restrictions) to allow developers full control - Add logging for final setting value and source (SharedPreferences/firebase.json/AndroidManifest/default) for diagnostics - Update firebase-schema.json: remove minimum/maximum constraints and update description - Update tests/firebase.json to use new key name Addresses review feedback: developers should have full control over the limit without artificial restrictions, with appropriate warnings in documentation. --- packages/app/firebase-schema.json | 8 +++----- .../ReactNativeFirebaseMessagingStoreImpl.java | 18 +++++++++++------- tests/firebase.json | 2 +- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/app/firebase-schema.json b/packages/app/firebase-schema.json index 421ae0129b..94fb479980 100644 --- a/packages/app/firebase-schema.json +++ b/packages/app/firebase-schema.json @@ -119,11 +119,9 @@ "description": "On iOS, indicating how to present a notification in a foreground app.", "type": "array" }, - "rn_firebase_messaging_max_stored_notifications": { - "description": "Controls how many notifications are retained on-device for background storage. Priority order is SharedPreferences -> firebase.json -> AndroidManifest. Values outside the 20-100 range are clamped to prevent excessive deletions or OOM.", - "type": "number", - "minimum": 20, - "maximum": 100 + "messaging_max_stored_notifications": { + "description": "Controls how many notifications are retained on-device for background storage. Configuration priority order is SharedPreferences -> firebase.json -> AndroidManifest. Default 100. Very small values may cause unwanted excessive deletions, large values or large message contents run the risk of OutOfMemoryErrors.", + "type": "number" }, "perf_auto_collection_enabled": { "description": "Disable or enable auto collection of performance monitoring data collection.\n This is useful for opt-in-first data flows, for example when dealing with GDPR compliance.\nThis can be overridden in JavaScript.", diff --git a/packages/messaging/android/src/main/java/io/invertase/firebase/messaging/ReactNativeFirebaseMessagingStoreImpl.java b/packages/messaging/android/src/main/java/io/invertase/firebase/messaging/ReactNativeFirebaseMessagingStoreImpl.java index abef67ca2d..92c39719c4 100644 --- a/packages/messaging/android/src/main/java/io/invertase/firebase/messaging/ReactNativeFirebaseMessagingStoreImpl.java +++ b/packages/messaging/android/src/main/java/io/invertase/firebase/messaging/ReactNativeFirebaseMessagingStoreImpl.java @@ -12,6 +12,8 @@ import org.json.JSONException; import org.json.JSONObject; +import android.util.Log; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -23,35 +25,37 @@ public class ReactNativeFirebaseMessagingStoreImpl implements ReactNativeFirebaseMessagingStore { - private static final String KEY_MAX_STORED_NOTIFICATIONS = "rn_firebase_messaging_max_stored_notifications"; + private static final String TAG = "RNFirebaseMsgStore"; + private static final String KEY_MAX_STORED_NOTIFICATIONS = "messaging_max_stored_notifications"; private static final String S_KEY_ALL_NOTIFICATION_IDS = "all_notification_ids"; private final String DELIMITER = ","; private static final int DEFAULT_MAX_SIZE_NOTIFICATIONS = 100; - private static final int MIN_SIZE_NOTIFICATIONS = 20; private static final int maxNotificationSize = resolveMaxNotificationSize(); private static int resolveMaxNotificationSize() { int maxSize = DEFAULT_MAX_SIZE_NOTIFICATIONS; + String source = "default"; ReactNativeFirebaseJSON json = ReactNativeFirebaseJSON.getSharedInstance(); ReactNativeFirebaseMeta meta = ReactNativeFirebaseMeta.getSharedInstance(); ReactNativeFirebasePreferences prefs = ReactNativeFirebasePreferences.getSharedInstance(); try { // Priority: SharedPreferences -> firebase.json -> AndroidManifest - // Note: ReactNativeFirebaseMeta doesn't have getIntValue, so we check meta.contains - // and read from AndroidManifest directly if needed if (prefs.contains(KEY_MAX_STORED_NOTIFICATIONS)) { maxSize = prefs.getIntValue(KEY_MAX_STORED_NOTIFICATIONS, DEFAULT_MAX_SIZE_NOTIFICATIONS); + source = "SharedPreferences"; } else if (json.contains(KEY_MAX_STORED_NOTIFICATIONS)) { maxSize = json.getIntValue(KEY_MAX_STORED_NOTIFICATIONS, DEFAULT_MAX_SIZE_NOTIFICATIONS); + source = "firebase.json"; } else if (meta.contains(KEY_MAX_STORED_NOTIFICATIONS)) { maxSize = meta.getIntValue(KEY_MAX_STORED_NOTIFICATIONS, DEFAULT_MAX_SIZE_NOTIFICATIONS); + source = "AndroidManifest"; } - // Clamp to allowed range to avoid OOM (upper) or mass deletions (lower) - return Math.max(MIN_SIZE_NOTIFICATIONS, Math.min(maxSize, DEFAULT_MAX_SIZE_NOTIFICATIONS)); + Log.d(TAG, "messaging_max_stored_notifications: " + maxSize + " (from " + source + ")"); + return maxSize; } catch (Exception e) { - // Ignore and use default + Log.w(TAG, "Error resolving max notification size, using default: " + DEFAULT_MAX_SIZE_NOTIFICATIONS, e); return DEFAULT_MAX_SIZE_NOTIFICATIONS; } } diff --git a/tests/firebase.json b/tests/firebase.json index 06b26454bc..e5ecd1c0fd 100644 --- a/tests/firebase.json +++ b/tests/firebase.json @@ -21,7 +21,7 @@ "messaging_android_notification_color": "@color/hotpink", "messaging_ios_auto_register_for_remote_messages": false, "messaging_android_notification_delegation_enabled": true, - "rn_firebase_messaging_max_stored_notifications": 50, + "messaging_max_stored_notifications": 100, "analytics_auto_collection_enabled": true, "analytics_collection_deactivated": false, From d796f99f4acae3ac8cc0c1224418a24bf1181b1f Mon Sep 17 00:00:00 2001 From: Sean Jung Date: Mon, 8 Dec 2025 15:03:54 +0900 Subject: [PATCH 5/5] fix(app, android): handle Integer values in ReactNativeFirebaseMeta.getAll() The getAll() method was only handling null, String, and Boolean values when converting metadata to a WritableMap. With the new getIntValue() method supporting integer configuration values (e.g., messaging_max_stored_notifications), Integer values in metadata were being silently skipped. Added Integer type check to properly include Integer values in the returned WritableMap. --- .../io/invertase/firebase/common/ReactNativeFirebaseMeta.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/app/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseMeta.java b/packages/app/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseMeta.java index 82c24ebce3..bce9bb5dc1 100644 --- a/packages/app/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseMeta.java +++ b/packages/app/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseMeta.java @@ -90,6 +90,8 @@ public WritableMap getAll() { map.putString(key, (String) value); } else if (value instanceof Boolean) { map.putBoolean(key, (Boolean) value); + } else if (value instanceof Integer) { + map.putInt(key, (Integer) value); } } }