From 2ca86dfd589216760f3fff052fd793dc9a4553aa Mon Sep 17 00:00:00 2001 From: Mike Hardy Date: Tue, 25 Nov 2025 20:20:13 -0500 Subject: [PATCH 1/5] refactor(messaging, android): rename notificationId-related items for clarity --- ...ReactNativeFirebaseMessagingStoreImpl.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 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 c7d2c5fb76..77f1058566 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 @@ -30,17 +30,17 @@ public void storeFirebaseMessage(RemoteMessage remoteMessage) { UniversalFirebasePreferences preferences = UniversalFirebasePreferences.getSharedInstance(); preferences.setStringValue(remoteMessage.getMessageId(), remoteMessageString); // save new notification id - String notifications = preferences.getStringValue(S_KEY_ALL_NOTIFICATION_IDS, ""); - notifications += remoteMessage.getMessageId() + DELIMITER; // append to last + String notificationIds = preferences.getStringValue(S_KEY_ALL_NOTIFICATION_IDS, ""); + notificationIds += remoteMessage.getMessageId() + DELIMITER; // append to last // check and remove old notifications message - List allNotificationList = convertToArray(notifications); + List allNotificationList = convertToArray(notificationIds); if (allNotificationList.size() > MAX_SIZE_NOTIFICATIONS) { String firstRemoteMessageId = allNotificationList.get(0); preferences.remove(firstRemoteMessageId); - notifications = removeRemoteMessage(firstRemoteMessageId, notifications); + notificationIds = removeRemoteMessageId(firstRemoteMessageId, notificationIds); } - preferences.setStringValue(S_KEY_ALL_NOTIFICATION_IDS, notifications); + preferences.setStringValue(S_KEY_ALL_NOTIFICATION_IDS, notificationIds); } catch (JSONException e) { e.printStackTrace(); } @@ -78,15 +78,15 @@ public void clearFirebaseMessage(String remoteMessageId) { UniversalFirebasePreferences preferences = UniversalFirebasePreferences.getSharedInstance(); preferences.remove(remoteMessageId); // check and remove old notifications message - String notifications = preferences.getStringValue(S_KEY_ALL_NOTIFICATION_IDS, ""); - if (!notifications.isEmpty()) { - notifications = removeRemoteMessage(remoteMessageId, notifications); // remove from list - preferences.setStringValue(S_KEY_ALL_NOTIFICATION_IDS, notifications); + String notificationIds = preferences.getStringValue(S_KEY_ALL_NOTIFICATION_IDS, ""); + if (!notificationIds.isEmpty()) { + notificationIds = removeRemoteMessageId(remoteMessageId, notificationIds); // remove from list + preferences.setStringValue(S_KEY_ALL_NOTIFICATION_IDS, notificationIds); } } - private String removeRemoteMessage(String remoteMessageId, String notifications) { - return notifications.replace(remoteMessageId + DELIMITER, ""); + private String removeRemoteMessageId(String remoteMessageId, String notificationIds) { + return notificationIds.replace(remoteMessageId + DELIMITER, ""); } private List convertToArray(String string) { From 0b895943c847ba03dd0b8e89f606b30286cdfeec Mon Sep 17 00:00:00 2001 From: Mike Hardy Date: Tue, 25 Nov 2025 20:21:44 -0500 Subject: [PATCH 2/5] refactor(messaging, android): centralize remote message removal logic it was duplicated, which means any fixes have to be in multiple spots --- .../messaging/ReactNativeFirebaseMessagingStoreImpl.java | 6 ++---- 1 file changed, 2 insertions(+), 4 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 77f1058566..474b1ccbb6 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 @@ -32,15 +32,13 @@ public void storeFirebaseMessage(RemoteMessage remoteMessage) { // save new notification id String notificationIds = preferences.getStringValue(S_KEY_ALL_NOTIFICATION_IDS, ""); notificationIds += remoteMessage.getMessageId() + DELIMITER; // append to last + preferences.setStringValue(S_KEY_ALL_NOTIFICATION_IDS, notificationIds); // check and remove old notifications message List allNotificationList = convertToArray(notificationIds); if (allNotificationList.size() > MAX_SIZE_NOTIFICATIONS) { - String firstRemoteMessageId = allNotificationList.get(0); - preferences.remove(firstRemoteMessageId); - notificationIds = removeRemoteMessageId(firstRemoteMessageId, notificationIds); + clearFirebaseMessage(allNotificationList.get(0)); } - preferences.setStringValue(S_KEY_ALL_NOTIFICATION_IDS, notificationIds); } catch (JSONException e) { e.printStackTrace(); } From 4c717b46cea4ee61c009b5d0b5dd5b3ffc42beeb Mon Sep 17 00:00:00 2001 From: Mike Hardy Date: Tue, 25 Nov 2025 20:23:02 -0500 Subject: [PATCH 3/5] fix(messaging, android): properly remove remote message from prefs previously the remove was not applied, so it likely never persisted to storage, leading to infinite growth and possible OOM --- .../invertase/firebase/common/UniversalFirebasePreferences.java | 2 ++ .../messaging/ReactNativeFirebaseMessagingStoreImpl.java | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/app/android/src/main/java/io/invertase/firebase/common/UniversalFirebasePreferences.java b/packages/app/android/src/main/java/io/invertase/firebase/common/UniversalFirebasePreferences.java index 2c7d0cad84..4fd60b88be 100644 --- a/packages/app/android/src/main/java/io/invertase/firebase/common/UniversalFirebasePreferences.java +++ b/packages/app/android/src/main/java/io/invertase/firebase/common/UniversalFirebasePreferences.java @@ -74,6 +74,8 @@ public void clearAll() { getPreferences().edit().clear().apply(); } + // Note the caller is responsible for calling apply() or commit() on the + // returned SharedPreferences.Editor object for the remove to take affect public SharedPreferences.Editor remove(String key) { return getPreferences().edit().remove(key); } 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 474b1ccbb6..6bb951472b 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 @@ -74,7 +74,7 @@ public WritableMap getFirebaseMessageMap(String remoteMessageId) { @Override public void clearFirebaseMessage(String remoteMessageId) { UniversalFirebasePreferences preferences = UniversalFirebasePreferences.getSharedInstance(); - preferences.remove(remoteMessageId); + preferences.remove(remoteMessageId).apply(); // check and remove old notifications message String notificationIds = preferences.getStringValue(S_KEY_ALL_NOTIFICATION_IDS, ""); if (!notificationIds.isEmpty()) { From 48e32dfc0bb281284f73ae4359421fdc6e80c26e Mon Sep 17 00:00:00 2001 From: Mike Hardy Date: Tue, 25 Nov 2025 20:24:19 -0500 Subject: [PATCH 4/5] fix(messaging, android): properly shrink stored messages to limit previously, stored messages were cleared one at a time each time a message came in, opening the possibility that the store would contain more messages then the limit and never get back down to the limit. now it will attempt to shrink down to the limit every time a message comes in ensuring that if there is an over limit condition it will be fixed quickly --- .../messaging/ReactNativeFirebaseMessagingStoreImpl.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 6bb951472b..534736ee8c 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 @@ -36,8 +36,9 @@ public void storeFirebaseMessage(RemoteMessage remoteMessage) { // check and remove old notifications message List allNotificationList = convertToArray(notificationIds); - if (allNotificationList.size() > MAX_SIZE_NOTIFICATIONS) { + while (allNotificationList.size() > MAX_SIZE_NOTIFICATIONS) { clearFirebaseMessage(allNotificationList.get(0)); + allNotificationList.remove(0); } } catch (JSONException e) { e.printStackTrace(); From 7e8975ec94c72989528808837192c24463477b72 Mon Sep 17 00:00:00 2001 From: Mike Hardy Date: Tue, 25 Nov 2025 20:48:39 -0500 Subject: [PATCH 5/5] fix(messaging, android): purge message store to limit first, add second previously we added before purging, so if close to an OOM we would trigger it, if an app update is pushed with this change, it will ideally recover completely from this situation by purging the store down before any new data is added --- .../ReactNativeFirebaseMessagingStoreImpl.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 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 534736ee8c..03cb8f2eaf 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 @@ -28,18 +28,21 @@ public void storeFirebaseMessage(RemoteMessage remoteMessage) { reactToJSON(remoteMessageToWritableMap(remoteMessage)).toString(); // Log.d("storeFirebaseMessage", remoteMessageString); UniversalFirebasePreferences preferences = UniversalFirebasePreferences.getSharedInstance(); - preferences.setStringValue(remoteMessage.getMessageId(), remoteMessageString); - // save new notification id - String notificationIds = preferences.getStringValue(S_KEY_ALL_NOTIFICATION_IDS, ""); - notificationIds += remoteMessage.getMessageId() + DELIMITER; // append to last - preferences.setStringValue(S_KEY_ALL_NOTIFICATION_IDS, notificationIds); - // check and remove old notifications message + // 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) { + while (allNotificationList.size() > MAX_SIZE_NOTIFICATIONS - 1) { clearFirebaseMessage(allNotificationList.get(0)); allNotificationList.remove(0); } + + // now refetch the ids after possible removals, and store the new message + notificationIds = preferences.getStringValue(S_KEY_ALL_NOTIFICATION_IDS, ""); + preferences.setStringValue(remoteMessage.getMessageId(), remoteMessageString); + // save new notification id + notificationIds += remoteMessage.getMessageId() + DELIMITER; // append to last + preferences.setStringValue(S_KEY_ALL_NOTIFICATION_IDS, notificationIds); } catch (JSONException e) { e.printStackTrace(); }