Skip to content

Commit

Permalink
fix(android, messaging): store notifications for initial/open attrib…
Browse files Browse the repository at this point in the history
…ution (#4317)

Use a 100-notification limited ring buffer in prefs to store them
  • Loading branch information
vannt1991 authored Sep 30, 2020
1 parent aa8ee8b commit a7cafc9
Show file tree
Hide file tree
Showing 6 changed files with 259 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package io.invertase.firebase.messaging;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import com.facebook.react.bridge.ReadableType;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.Iterator;

public abstract class JsonConvert {
public static JSONObject reactToJSON(ReadableMap readableMap) throws JSONException {
JSONObject jsonObject = new JSONObject();
ReadableMapKeySetIterator iterator = readableMap.keySetIterator();
while(iterator.hasNextKey()){
String key = iterator.nextKey();
ReadableType valueType = readableMap.getType(key);
switch (valueType){
case Null:
jsonObject.put(key,JSONObject.NULL);
break;
case Boolean:
jsonObject.put(key, readableMap.getBoolean(key));
break;
case Number:
try {
jsonObject.put(key, readableMap.getInt(key));
} catch(Exception e) {
jsonObject.put(key, readableMap.getDouble(key));
}
break;
case String:
jsonObject.put(key, readableMap.getString(key));
break;
case Map:
jsonObject.put(key, reactToJSON(readableMap.getMap(key)));
break;
case Array:
jsonObject.put(key, reactToJSON(readableMap.getArray(key)));
break;
}
}

return jsonObject;
}

public static JSONArray reactToJSON(ReadableArray readableArray) throws JSONException {
JSONArray jsonArray = new JSONArray();
for(int i=0; i < readableArray.size(); i++) {
ReadableType valueType = readableArray.getType(i);
switch (valueType){
case Null:
jsonArray.put(JSONObject.NULL);
break;
case Boolean:
jsonArray.put(readableArray.getBoolean(i));
break;
case Number:
try {
jsonArray.put(readableArray.getInt(i));
} catch(Exception e) {
jsonArray.put(readableArray.getDouble(i));
}
break;
case String:
jsonArray.put(readableArray.getString(i));
break;
case Map:
jsonArray.put(reactToJSON(readableArray.getMap(i)));
break;
case Array:
jsonArray.put(reactToJSON(readableArray.getArray(i)));
break;
}
}
return jsonArray;
}

public static WritableMap jsonToReact(JSONObject jsonObject) throws JSONException {
WritableMap writableMap = Arguments.createMap();
Iterator iterator = jsonObject.keys();
while(iterator.hasNext()) {
String key = (String) iterator.next();
Object value = jsonObject.get(key);
if (value instanceof Float || value instanceof Double) {
writableMap.putDouble(key, jsonObject.getDouble(key));
} else if (value instanceof Number) {
writableMap.putInt(key, jsonObject.getInt(key));
} else if (value instanceof String) {
writableMap.putString(key, jsonObject.getString(key));
} else if (value instanceof JSONObject) {
writableMap.putMap(key,jsonToReact(jsonObject.getJSONObject(key)));
} else if (value instanceof JSONArray){
writableMap.putArray(key, jsonToReact(jsonObject.getJSONArray(key)));
} else if (value == JSONObject.NULL){
writableMap.putNull(key);
}
}

return writableMap;
}

public static WritableArray jsonToReact(JSONArray jsonArray) throws JSONException {
WritableArray writableArray = Arguments.createArray();
for(int i=0; i < jsonArray.length(); i++) {
Object value = jsonArray.get(i);
if (value instanceof Float || value instanceof Double) {
writableArray.pushDouble(jsonArray.getDouble(i));
} else if (value instanceof Number) {
writableArray.pushInt(jsonArray.getInt(i));
} else if (value instanceof String) {
writableArray.pushString(jsonArray.getString(i));
} else if (value instanceof JSONObject) {
writableArray.pushMap(jsonToReact(jsonArray.getJSONObject(i)));
} else if (value instanceof JSONArray){
writableArray.pushArray(jsonToReact(jsonArray.getJSONArray(i)));
} else if (value == JSONObject.NULL){
writableArray.pushNull();
}
}
return writableArray;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,11 @@ public void getInitialNotification(Promise promise) {
// only handle non-consumed initial notifications
if (messageId != null && initialNotificationMap.get(messageId) == null) {
RemoteMessage remoteMessage = ReactNativeFirebaseMessagingReceiver.notifications.get(messageId);

if (remoteMessage == null) {
ReactNativeFirebaseMessagingStore messagingStore = ReactNativeFirebaseMessagingStoreHelper.getInstance().getMessagingStore();
remoteMessage = messagingStore.getFirebaseMessage(messageId);
messagingStore.clearFirebaseMessage(messageId);
}
if (remoteMessage != null) {
promise.resolve(ReactNativeFirebaseMessagingSerializer.remoteMessageToWritableMap(remoteMessage));
initialNotificationMap.put(messageId, true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import java.util.HashMap;

import io.invertase.firebase.app.ReactNativeFirebaseApp;
import io.invertase.firebase.common.ReactNativeFirebaseEventEmitter;
import io.invertase.firebase.common.SharedUtils;

Expand All @@ -22,13 +23,16 @@ public class ReactNativeFirebaseMessagingReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "broadcast received for message");

if (ReactNativeFirebaseApp.getApplicationContext() == null) {
ReactNativeFirebaseApp.setApplicationContext(context.getApplicationContext());
}
RemoteMessage remoteMessage = new RemoteMessage(intent.getExtras());
ReactNativeFirebaseEventEmitter emitter = ReactNativeFirebaseEventEmitter.getSharedInstance();

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

// |-> ---------------------
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.invertase.firebase.messaging;

import com.google.firebase.messaging.RemoteMessage;

public interface ReactNativeFirebaseMessagingStore {
void storeFirebaseMessage(RemoteMessage remoteMessage);

RemoteMessage getFirebaseMessage(String remoteMessageId);

void clearFirebaseMessage(String remoteMessageId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.invertase.firebase.messaging;

public class ReactNativeFirebaseMessagingStoreHelper {

private ReactNativeFirebaseMessagingStore messagingStore;

private ReactNativeFirebaseMessagingStoreHelper() {
messagingStore = new ReactNativeFirebaseMessagingStoreImpl();
}

private static ReactNativeFirebaseMessagingStoreHelper _instance;

public static ReactNativeFirebaseMessagingStoreHelper getInstance() {
if (_instance == null) {
_instance = new ReactNativeFirebaseMessagingStoreHelper();
}
return _instance;
}

public ReactNativeFirebaseMessagingStore getMessagingStore() {
return messagingStore;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package io.invertase.firebase.messaging;

import com.facebook.react.bridge.WritableMap;
import com.google.firebase.messaging.RemoteMessage;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import io.invertase.firebase.common.UniversalFirebasePreferences;

import static io.invertase.firebase.messaging.JsonConvert.jsonToReact;
import static io.invertase.firebase.messaging.JsonConvert.reactToJSON;
import static io.invertase.firebase.messaging.ReactNativeFirebaseMessagingSerializer.remoteMessageFromReadableMap;
import static io.invertase.firebase.messaging.ReactNativeFirebaseMessagingSerializer.remoteMessageToWritableMap;

public class ReactNativeFirebaseMessagingStoreImpl implements ReactNativeFirebaseMessagingStore {

private static final String S_KEY_ALL_NOTIFICATION_IDS = "all_notification_ids";
private static final int MAX_SIZE_NOTIFICATIONS = 100;
private final String DELIMITER = ",";

@Override
public void storeFirebaseMessage(RemoteMessage remoteMessage) {
try {
String remoteMessageString = reactToJSON(remoteMessageToWritableMap(remoteMessage)).toString();
// Log.d("storeFirebaseMessage", remoteMessageString);
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

// check and remove old notifications message
List<String> allNotificationList = convertToArray(notifications);
if (allNotificationList.size() > MAX_SIZE_NOTIFICATIONS) {
String firstRemoteMessageId = allNotificationList.get(0);
preferences.remove(firstRemoteMessageId);
notifications = removeRemoteMessage(firstRemoteMessageId, notifications);
}
preferences.setStringValue(S_KEY_ALL_NOTIFICATION_IDS, notifications);
} catch (JSONException e) {
e.printStackTrace();
}
}

@Override
public RemoteMessage getFirebaseMessage(String remoteMessageId) {
String remoteMessageString = UniversalFirebasePreferences.getSharedInstance().getStringValue(remoteMessageId, null);
if (remoteMessageString != null) {
// Log.d("getFirebaseMessage", remoteMessageString);
try {
WritableMap readableMap = jsonToReact(new JSONObject(remoteMessageString));
readableMap.putString("to", remoteMessageId);//fake to
return remoteMessageFromReadableMap(readableMap);
} catch (JSONException e) {
e.printStackTrace();
}
}
return null;
}

@Override
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);
}
}

private String removeRemoteMessage(String remoteMessageId, String notifications) {
return notifications.replace(remoteMessageId + DELIMITER, "");
}

private List<String> convertToArray(String string) {
return new ArrayList<>(Arrays.asList(string.split(DELIMITER)));
}

}

1 comment on commit a7cafc9

@vercel
Copy link

@vercel vercel bot commented on a7cafc9 Sep 30, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.