Skip to content

Commit 3c36fb6

Browse files
authored
fix(android): Push notification crashing on some edge cases (#6667)
1 parent 899d21b commit 3c36fb6

File tree

1 file changed

+50
-18
lines changed

1 file changed

+50
-18
lines changed

android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@
4242
public class CustomPushNotification extends PushNotification {
4343
public static ReactApplicationContext reactApplicationContext;
4444
final NotificationManager notificationManager;
45+
46+
// Create a single Gson instance
47+
private static final Gson gson = new Gson();
4548

4649
public CustomPushNotification(Context context, Bundle bundle, AppLifecycleFacade appLifecycleFacade, AppLaunchHelper appLaunchHelper, JsIOHelper jsIoHelper) {
4750
super(context, bundle, appLifecycleFacade, appLaunchHelper, jsIoHelper);
@@ -63,9 +66,9 @@ public static void clearMessages(int notId) {
6366
@Override
6467
public void onReceived() throws InvalidNotificationException {
6568
Bundle received = mNotificationProps.asBundle();
66-
Ejson receivedEjson = new Gson().fromJson(received.getString("ejson", "{}"), Ejson.class);
69+
Ejson receivedEjson = safeFromJson(received.getString("ejson", "{}"), Ejson.class);
6770

68-
if (receivedEjson.notificationType != null && receivedEjson.notificationType.equals("message-id-only")) {
71+
if (receivedEjson != null && receivedEjson.notificationType != null && receivedEjson.notificationType.equals("message-id-only")) {
6972
notificationLoad(receivedEjson, new Callback() {
7073
@Override
7174
public void call(@Nullable Bundle bundle) {
@@ -78,18 +81,18 @@ public void call(@Nullable Bundle bundle) {
7881

7982
// We should re-read these values since that can be changed by notificationLoad
8083
Bundle bundle = mNotificationProps.asBundle();
81-
Ejson loadedEjson = new Gson().fromJson(bundle.getString("ejson", "{}"), Ejson.class);
84+
Ejson loadedEjson = safeFromJson(bundle.getString("ejson", "{}"), Ejson.class);
8285
String notId = bundle.getString("notId", "1");
8386

8487
if (notificationMessages.get(notId) == null) {
8588
notificationMessages.put(notId, new ArrayList<Bundle>());
8689
}
8790

88-
boolean hasSender = loadedEjson.sender != null;
91+
boolean hasSender = loadedEjson != null && loadedEjson.sender != null;
8992
String title = bundle.getString("title");
9093

9194
// If it has a encrypted message
92-
if (loadedEjson.msg != null) {
95+
if (loadedEjson != null && loadedEjson.msg != null) {
9396
// Override message with the decrypted content
9497
String decrypted = Encryption.shared.decryptMessage(loadedEjson, reactApplicationContext);
9598
if (decrypted != null) {
@@ -100,9 +103,9 @@ public void call(@Nullable Bundle bundle) {
100103
bundle.putLong("time", new Date().getTime());
101104
bundle.putString("username", hasSender ? loadedEjson.sender.username : title);
102105
bundle.putString("senderId", hasSender ? loadedEjson.sender._id : "1");
103-
bundle.putString("avatarUri", loadedEjson.getAvatarUri());
106+
bundle.putString("avatarUri", loadedEjson != null ? loadedEjson.getAvatarUri() : null);
104107

105-
if (loadedEjson.notificationType instanceof String && loadedEjson.notificationType.equals("videoconf")) {
108+
if (loadedEjson != null && loadedEjson.notificationType instanceof String && loadedEjson.notificationType.equals("videoconf")) {
106109
notifyReceivedToJS();
107110
} else {
108111
notificationMessages.get(notId).add(bundle);
@@ -128,7 +131,7 @@ protected Notification.Builder getNotificationBuilder(PendingIntent intent) {
128131
String title = bundle.getString("title");
129132
String message = bundle.getString("message");
130133
Boolean notificationLoaded = bundle.getBoolean("notificationLoaded", false);
131-
Ejson ejson = new Gson().fromJson(bundle.getString("ejson", "{}"), Ejson.class);
134+
Ejson ejson = safeFromJson(bundle.getString("ejson", "{}"), Ejson.class);
132135

133136
notification
134137
.setContentTitle(title)
@@ -145,7 +148,7 @@ protected Notification.Builder getNotificationBuilder(PendingIntent intent) {
145148
notificationDismiss(notification, notificationId);
146149

147150
// if notificationType is null (RC < 3.5) or notificationType is different of message-id-only or notification was loaded successfully
148-
if (ejson.notificationType == null || !ejson.notificationType.equals("message-id-only") || notificationLoaded) {
151+
if (ejson == null || ejson.notificationType == null || !ejson.notificationType.equals("message-id-only") || notificationLoaded) {
149152
notificationStyle(notification, notificationId, bundle);
150153
notificationReply(notification, notificationId, bundle);
151154

@@ -159,9 +162,9 @@ protected Notification.Builder getNotificationBuilder(PendingIntent intent) {
159162
while (iterator.hasNext()) {
160163
Bundle not = (Bundle) iterator.next();
161164
// get the notification info
162-
Ejson notEjson = gson.fromJson(not.getString("ejson", "{}"), Ejson.class);
165+
Ejson notEjson = safeFromJson(not.getString("ejson", "{}"), Ejson.class);
163166
// if already has a notification from same server
164-
if (ejson.serverURL().equals(notEjson.serverURL())) {
167+
if (ejson != null && notEjson != null && ejson.serverURL().equals(notEjson.serverURL())) {
165168
String id = not.getString("notId");
166169
// cancel this notification
167170
notificationManager.cancel(Integer.parseInt(id));
@@ -207,13 +210,15 @@ private void notificationIcons(Notification.Builder notification, Bundle bundle)
207210

208211
int smallIconResId = res.getIdentifier("ic_notification", "drawable", packageName);
209212

210-
Gson gson = new Gson();
211-
Ejson ejson = gson.fromJson(bundle.getString("ejson", "{}"), Ejson.class);
213+
Ejson ejson = safeFromJson(bundle.getString("ejson", "{}"), Ejson.class);
212214

213215
notification.setSmallIcon(smallIconResId);
214216

215217
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
216-
notification.setLargeIcon(getAvatar(ejson.getAvatarUri()));
218+
String avatarUri = ejson != null ? ejson.getAvatarUri() : null;
219+
if (avatarUri != null) {
220+
notification.setLargeIcon(getAvatar(avatarUri));
221+
}
217222
}
218223
}
219224

@@ -236,7 +241,7 @@ private void notificationChannel(Notification.Builder notification) {
236241
}
237242

238243
private String extractMessage(String message, Ejson ejson) {
239-
if (ejson.type != null && !ejson.type.equals("d")) {
244+
if (ejson != null && ejson.type != null && !ejson.type.equals("d")) {
240245
int pos = message.indexOf(":");
241246
int start = pos == -1 ? 0 : pos + 2;
242247
return message.substring(start, message.length());
@@ -291,17 +296,19 @@ private void notificationStyle(Notification.Builder notification, int notId, Bun
291296
String message = data.getString("message");
292297
String senderId = data.getString("senderId");
293298
String avatarUri = data.getString("avatarUri");
294-
Ejson ejson = gson.fromJson(data.getString("ejson", "{}"), Ejson.class);
299+
Ejson ejson = safeFromJson(data.getString("ejson", "{}"), Ejson.class);
295300
String m = extractMessage(message, ejson);
296301

297302
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
298-
messageStyle.addMessage(m, timestamp, ejson.senderName);
303+
String senderName = ejson != null ? ejson.senderName : "Unknown";
304+
messageStyle.addMessage(m, timestamp, senderName);
299305
} else {
300306
Bitmap avatar = getAvatar(avatarUri);
301307

308+
String senderName = ejson != null ? ejson.senderName : "Unknown";
302309
Person.Builder sender = new Person.Builder()
303310
.setKey(senderId)
304-
.setName(ejson.senderName);
311+
.setName(senderName);
305312

306313
if (avatar != null) {
307314
sender.setIcon(Icon.createWithBitmap(avatar));
@@ -369,4 +376,29 @@ private void notificationLoad(Ejson ejson, Callback callback) {
369376
LoadNotification loadNotification = new LoadNotification();
370377
loadNotification.load(reactApplicationContext, ejson, callback);
371378
}
379+
380+
/**
381+
* Safely parse JSON string to object with error handling.
382+
*
383+
* @param json JSON string to parse
384+
* @param classOfT Target class type
385+
* @param <T> Type parameter
386+
* @return Parsed object, or null if parsing fails
387+
*/
388+
private static <T> T safeFromJson(String json, Class<T> classOfT) {
389+
if (json == null || json.trim().isEmpty() || json.equals("{}")) {
390+
return null; // no need to create a new instance
391+
}
392+
393+
try {
394+
return gson.fromJson(json, classOfT);
395+
} catch (Exception e) {
396+
android.util.Log.e(
397+
"CustomPushNotification",
398+
"Failed to parse JSON into " + classOfT.getSimpleName() + " (payload redacted).",
399+
e
400+
);
401+
return null;
402+
}
403+
}
372404
}

0 commit comments

Comments
 (0)