Skip to content

Commit a7cafc9

Browse files
authored
fix(android, messaging): store notifications for initial/open attribution (#4317)
Use a 100-notification limited ring buffer in prefs to store them
1 parent aa8ee8b commit a7cafc9

File tree

6 files changed

+259
-2
lines changed

6 files changed

+259
-2
lines changed
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package io.invertase.firebase.messaging;
2+
3+
import com.facebook.react.bridge.Arguments;
4+
import com.facebook.react.bridge.ReadableArray;
5+
import com.facebook.react.bridge.ReadableMap;
6+
import com.facebook.react.bridge.ReadableMapKeySetIterator;
7+
import com.facebook.react.bridge.ReadableType;
8+
import com.facebook.react.bridge.WritableArray;
9+
import com.facebook.react.bridge.WritableMap;
10+
11+
import org.json.JSONArray;
12+
import org.json.JSONException;
13+
import org.json.JSONObject;
14+
15+
import java.util.Iterator;
16+
17+
public abstract class JsonConvert {
18+
public static JSONObject reactToJSON(ReadableMap readableMap) throws JSONException {
19+
JSONObject jsonObject = new JSONObject();
20+
ReadableMapKeySetIterator iterator = readableMap.keySetIterator();
21+
while(iterator.hasNextKey()){
22+
String key = iterator.nextKey();
23+
ReadableType valueType = readableMap.getType(key);
24+
switch (valueType){
25+
case Null:
26+
jsonObject.put(key,JSONObject.NULL);
27+
break;
28+
case Boolean:
29+
jsonObject.put(key, readableMap.getBoolean(key));
30+
break;
31+
case Number:
32+
try {
33+
jsonObject.put(key, readableMap.getInt(key));
34+
} catch(Exception e) {
35+
jsonObject.put(key, readableMap.getDouble(key));
36+
}
37+
break;
38+
case String:
39+
jsonObject.put(key, readableMap.getString(key));
40+
break;
41+
case Map:
42+
jsonObject.put(key, reactToJSON(readableMap.getMap(key)));
43+
break;
44+
case Array:
45+
jsonObject.put(key, reactToJSON(readableMap.getArray(key)));
46+
break;
47+
}
48+
}
49+
50+
return jsonObject;
51+
}
52+
53+
public static JSONArray reactToJSON(ReadableArray readableArray) throws JSONException {
54+
JSONArray jsonArray = new JSONArray();
55+
for(int i=0; i < readableArray.size(); i++) {
56+
ReadableType valueType = readableArray.getType(i);
57+
switch (valueType){
58+
case Null:
59+
jsonArray.put(JSONObject.NULL);
60+
break;
61+
case Boolean:
62+
jsonArray.put(readableArray.getBoolean(i));
63+
break;
64+
case Number:
65+
try {
66+
jsonArray.put(readableArray.getInt(i));
67+
} catch(Exception e) {
68+
jsonArray.put(readableArray.getDouble(i));
69+
}
70+
break;
71+
case String:
72+
jsonArray.put(readableArray.getString(i));
73+
break;
74+
case Map:
75+
jsonArray.put(reactToJSON(readableArray.getMap(i)));
76+
break;
77+
case Array:
78+
jsonArray.put(reactToJSON(readableArray.getArray(i)));
79+
break;
80+
}
81+
}
82+
return jsonArray;
83+
}
84+
85+
public static WritableMap jsonToReact(JSONObject jsonObject) throws JSONException {
86+
WritableMap writableMap = Arguments.createMap();
87+
Iterator iterator = jsonObject.keys();
88+
while(iterator.hasNext()) {
89+
String key = (String) iterator.next();
90+
Object value = jsonObject.get(key);
91+
if (value instanceof Float || value instanceof Double) {
92+
writableMap.putDouble(key, jsonObject.getDouble(key));
93+
} else if (value instanceof Number) {
94+
writableMap.putInt(key, jsonObject.getInt(key));
95+
} else if (value instanceof String) {
96+
writableMap.putString(key, jsonObject.getString(key));
97+
} else if (value instanceof JSONObject) {
98+
writableMap.putMap(key,jsonToReact(jsonObject.getJSONObject(key)));
99+
} else if (value instanceof JSONArray){
100+
writableMap.putArray(key, jsonToReact(jsonObject.getJSONArray(key)));
101+
} else if (value == JSONObject.NULL){
102+
writableMap.putNull(key);
103+
}
104+
}
105+
106+
return writableMap;
107+
}
108+
109+
public static WritableArray jsonToReact(JSONArray jsonArray) throws JSONException {
110+
WritableArray writableArray = Arguments.createArray();
111+
for(int i=0; i < jsonArray.length(); i++) {
112+
Object value = jsonArray.get(i);
113+
if (value instanceof Float || value instanceof Double) {
114+
writableArray.pushDouble(jsonArray.getDouble(i));
115+
} else if (value instanceof Number) {
116+
writableArray.pushInt(jsonArray.getInt(i));
117+
} else if (value instanceof String) {
118+
writableArray.pushString(jsonArray.getString(i));
119+
} else if (value instanceof JSONObject) {
120+
writableArray.pushMap(jsonToReact(jsonArray.getJSONObject(i)));
121+
} else if (value instanceof JSONArray){
122+
writableArray.pushArray(jsonToReact(jsonArray.getJSONArray(i)));
123+
} else if (value == JSONObject.NULL){
124+
writableArray.pushNull();
125+
}
126+
}
127+
return writableArray;
128+
}
129+
}

packages/messaging/android/src/main/java/io/invertase/firebase/messaging/ReactNativeFirebaseMessagingModule.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,11 @@ public void getInitialNotification(Promise promise) {
6969
// only handle non-consumed initial notifications
7070
if (messageId != null && initialNotificationMap.get(messageId) == null) {
7171
RemoteMessage remoteMessage = ReactNativeFirebaseMessagingReceiver.notifications.get(messageId);
72-
72+
if (remoteMessage == null) {
73+
ReactNativeFirebaseMessagingStore messagingStore = ReactNativeFirebaseMessagingStoreHelper.getInstance().getMessagingStore();
74+
remoteMessage = messagingStore.getFirebaseMessage(messageId);
75+
messagingStore.clearFirebaseMessage(messageId);
76+
}
7377
if (remoteMessage != null) {
7478
promise.resolve(ReactNativeFirebaseMessagingSerializer.remoteMessageToWritableMap(remoteMessage));
7579
initialNotificationMap.put(messageId, true);

packages/messaging/android/src/main/java/io/invertase/firebase/messaging/ReactNativeFirebaseMessagingReceiver.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import java.util.HashMap;
1313

14+
import io.invertase.firebase.app.ReactNativeFirebaseApp;
1415
import io.invertase.firebase.common.ReactNativeFirebaseEventEmitter;
1516
import io.invertase.firebase.common.SharedUtils;
1617

@@ -22,13 +23,16 @@ public class ReactNativeFirebaseMessagingReceiver extends BroadcastReceiver {
2223
@Override
2324
public void onReceive(Context context, Intent intent) {
2425
Log.d(TAG, "broadcast received for message");
25-
26+
if (ReactNativeFirebaseApp.getApplicationContext() == null) {
27+
ReactNativeFirebaseApp.setApplicationContext(context.getApplicationContext());
28+
}
2629
RemoteMessage remoteMessage = new RemoteMessage(intent.getExtras());
2730
ReactNativeFirebaseEventEmitter emitter = ReactNativeFirebaseEventEmitter.getSharedInstance();
2831

2932
// Add a RemoteMessage if the message contains a notification payload
3033
if (remoteMessage.getNotification() != null) {
3134
notifications.put(remoteMessage.getMessageId(), remoteMessage);
35+
ReactNativeFirebaseMessagingStoreHelper.getInstance().getMessagingStore().storeFirebaseMessage(remoteMessage);
3236
}
3337

3438
// |-> ---------------------
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package io.invertase.firebase.messaging;
2+
3+
import com.google.firebase.messaging.RemoteMessage;
4+
5+
public interface ReactNativeFirebaseMessagingStore {
6+
void storeFirebaseMessage(RemoteMessage remoteMessage);
7+
8+
RemoteMessage getFirebaseMessage(String remoteMessageId);
9+
10+
void clearFirebaseMessage(String remoteMessageId);
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package io.invertase.firebase.messaging;
2+
3+
public class ReactNativeFirebaseMessagingStoreHelper {
4+
5+
private ReactNativeFirebaseMessagingStore messagingStore;
6+
7+
private ReactNativeFirebaseMessagingStoreHelper() {
8+
messagingStore = new ReactNativeFirebaseMessagingStoreImpl();
9+
}
10+
11+
private static ReactNativeFirebaseMessagingStoreHelper _instance;
12+
13+
public static ReactNativeFirebaseMessagingStoreHelper getInstance() {
14+
if (_instance == null) {
15+
_instance = new ReactNativeFirebaseMessagingStoreHelper();
16+
}
17+
return _instance;
18+
}
19+
20+
public ReactNativeFirebaseMessagingStore getMessagingStore() {
21+
return messagingStore;
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package io.invertase.firebase.messaging;
2+
3+
import com.facebook.react.bridge.WritableMap;
4+
import com.google.firebase.messaging.RemoteMessage;
5+
6+
import org.json.JSONException;
7+
import org.json.JSONObject;
8+
9+
import java.util.ArrayList;
10+
import java.util.Arrays;
11+
import java.util.List;
12+
13+
import io.invertase.firebase.common.UniversalFirebasePreferences;
14+
15+
import static io.invertase.firebase.messaging.JsonConvert.jsonToReact;
16+
import static io.invertase.firebase.messaging.JsonConvert.reactToJSON;
17+
import static io.invertase.firebase.messaging.ReactNativeFirebaseMessagingSerializer.remoteMessageFromReadableMap;
18+
import static io.invertase.firebase.messaging.ReactNativeFirebaseMessagingSerializer.remoteMessageToWritableMap;
19+
20+
public class ReactNativeFirebaseMessagingStoreImpl implements ReactNativeFirebaseMessagingStore {
21+
22+
private static final String S_KEY_ALL_NOTIFICATION_IDS = "all_notification_ids";
23+
private static final int MAX_SIZE_NOTIFICATIONS = 100;
24+
private final String DELIMITER = ",";
25+
26+
@Override
27+
public void storeFirebaseMessage(RemoteMessage remoteMessage) {
28+
try {
29+
String remoteMessageString = reactToJSON(remoteMessageToWritableMap(remoteMessage)).toString();
30+
// Log.d("storeFirebaseMessage", remoteMessageString);
31+
UniversalFirebasePreferences preferences = UniversalFirebasePreferences.getSharedInstance();
32+
preferences.setStringValue(remoteMessage.getMessageId(), remoteMessageString);
33+
// save new notification id
34+
String notifications = preferences.getStringValue(S_KEY_ALL_NOTIFICATION_IDS, "");
35+
notifications += remoteMessage.getMessageId() + DELIMITER; // append to last
36+
37+
// check and remove old notifications message
38+
List<String> allNotificationList = convertToArray(notifications);
39+
if (allNotificationList.size() > MAX_SIZE_NOTIFICATIONS) {
40+
String firstRemoteMessageId = allNotificationList.get(0);
41+
preferences.remove(firstRemoteMessageId);
42+
notifications = removeRemoteMessage(firstRemoteMessageId, notifications);
43+
}
44+
preferences.setStringValue(S_KEY_ALL_NOTIFICATION_IDS, notifications);
45+
} catch (JSONException e) {
46+
e.printStackTrace();
47+
}
48+
}
49+
50+
@Override
51+
public RemoteMessage getFirebaseMessage(String remoteMessageId) {
52+
String remoteMessageString = UniversalFirebasePreferences.getSharedInstance().getStringValue(remoteMessageId, null);
53+
if (remoteMessageString != null) {
54+
// Log.d("getFirebaseMessage", remoteMessageString);
55+
try {
56+
WritableMap readableMap = jsonToReact(new JSONObject(remoteMessageString));
57+
readableMap.putString("to", remoteMessageId);//fake to
58+
return remoteMessageFromReadableMap(readableMap);
59+
} catch (JSONException e) {
60+
e.printStackTrace();
61+
}
62+
}
63+
return null;
64+
}
65+
66+
@Override
67+
public void clearFirebaseMessage(String remoteMessageId) {
68+
UniversalFirebasePreferences preferences = UniversalFirebasePreferences.getSharedInstance();
69+
preferences.remove(remoteMessageId);
70+
// check and remove old notifications message
71+
String notifications = preferences.getStringValue(S_KEY_ALL_NOTIFICATION_IDS, "");
72+
if (!notifications.isEmpty()) {
73+
notifications = removeRemoteMessage(remoteMessageId, notifications); // remove from list
74+
preferences.setStringValue(S_KEY_ALL_NOTIFICATION_IDS, notifications);
75+
}
76+
}
77+
78+
private String removeRemoteMessage(String remoteMessageId, String notifications) {
79+
return notifications.replace(remoteMessageId + DELIMITER, "");
80+
}
81+
82+
private List<String> convertToArray(String string) {
83+
return new ArrayList<>(Arrays.asList(string.split(DELIMITER)));
84+
}
85+
86+
}

0 commit comments

Comments
 (0)