invertase / notifee

⚛️ A feature rich notifications library for React Native.
https://notifee.app
Apache License 2.0
1.86k stars 225 forks source link

Notifee does not support extra params from Android intent back to JS #1081

Closed aureosouza closed 4 weeks ago

aureosouza commented 2 months ago

@helenaford based on discussions from here and using similar solution as here. we are trying to fetch the custom data on Android overriding the handleIntent method from ReactNativeFirebaseMessagingService (react-native-firebase). As we won't be sending data only notifications to solve this currently, so just like iOS, we want to try to get from same notification:

public class ReactNativeFirebaseMessagingService extends FirebaseMessagingService {
...
@Override
  public void handleIntent(Intent intent) {
    Bundle extras = intent.getExtras();

    try {
      RemoteMessage message = new RemoteMessage(extras);
      RemoteMessage.Notification notificationData = message.getNotification();

      // Short-circuit if this is not a display notification
      if (notificationData == null) {
        super.handleIntent(intent);
        return;
      }

      // 1. Filter the notification as needed using data payload, clickAction, etc.
      if (message.getData().containsKey("mydata")) {
        // 2. Manually display notifications that match your filter
        Map<String, String> data = message.getData();

        // Create an intent that will be triggered when the user clicks the notification
        for (Map.Entry<String, String> entry : data.entrySet()) {
          intent.putExtra(entry.getKey(), entry.getValue());
        }
        super.handleIntent(intent);
        return;
      }

    } catch (Exception e) {
      super.handleIntent(intent);
      return;
    }

    // 3. Fall-through to this for everything else
    super.handleIntent(intent);
  }
  ...
}

And we are able to see our data correctly when logging in native, when adding extras to intent here intent.putExtra(entry.getKey(), entry.getValue());. The problem is that when we call on JS side:

   import notifee from '@notifee/react-native';
   ...
   const notifications = await notifee.getDisplayedNotifications();

Our notifications does not have the data params, we did some investigation on notifee code and noticed when calling getDisplayedNotifications on native module we fetch this List:

 public static List b() throws Exception {
        ArrayList var0;
        var0 = new ArrayList.<init>();
        if (VERSION.SDK_INT < 23) {
            return var0;
        } else {
            StatusBarNotification[] var1;
            int var2 = (var1 = ((NotificationManager)e.a.getSystemService("notification")).getActiveNotifications()).length;

            for(int var3 = 0; var3 < var2; ++var3) {
                StatusBarNotification var4;
                Notification var5;
                Bundle var6;
                Bundle var10000 = var6 = (var5 = (var4 = var1[var3]).getNotification()).extras;
                Bundle var7;
                var7 = new Bundle.<init>();
                Bundle var8;
                var10000 = var8 = var10000.getBundle("notifee.notification");
                Bundle var9 = var6.getBundle("notifee.trigger");
                if (var10000 == null) {
                    Bundle var10001 = var8 = new Bundle;
                    var10001.<init>();
                    var10001.putString("id", "" + var4.getId());
                    Object var10;
                    if ((var10 = var6.get("android.title")) != null) {
                        var8.putString("title", var10.toString());
                    }

                    if ((var10 = var6.get("android.text")) != null) {
                        var8.putString("body", var10.toString());
                    }

                    Object var11;
                    if ((var11 = var6.get("android.subText")) != null) {
                        var8.putString("subtitle", var11.toString());
                    }

                    var6 = new Bundle.<init>();
                    if (VERSION.SDK_INT >= 26) {
                        var6.putString("channelId", var5.getChannelId());
                    }

                    var6.putString("tag", var4.getTag());
                    var6.putString("group", var5.getGroup());
                    var8.putBundle("android", var6);
                    var7.putString("id", "" + var4.getId());
                } else {
                    var7.putString("id", "" + var8.get("id"));
                }

                if (var9 != null) {
                    var7.putBundle("trigger", var9);
                }

                var7.putBundle("notification", var8);
                var7.putString("date", "" + var4.getPostTime());
                var0.add(var7);
            }

            return var0;
        }
    }

And there is only a strict specific set of variables being sent using putString and putBundle. Is there a way for notifee to pass any extra params from intent back to JS, if they exist?

aureosouza commented 2 months ago

FYI we had to implement a patch for this, we've added an extra fetchNotificationData to getDisplayedNotifications module method, that injects the data param from firebase (if it exists):

private List<Bundle> fetchNotificationData(List<Bundle> aBundleList) {
    NotificationManager notificationManager = (NotificationManager) getReactApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
    if (notificationManager != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
      StatusBarNotification[] activeNotifications = notificationManager.getActiveNotifications();

      for (StatusBarNotification sbn : activeNotifications) {
        Notification notification = sbn.getNotification();
        Bundle extras = notification.extras;

        if (extras != null) {
          Bundle data = extras.getBundle("data");
          if (data != null) {
            for (Bundle originalBundle : aBundleList) {
              Bundle originalNotificationBundle = originalBundle.getBundle("notification");
              if (originalNotificationBundle != null && originalNotificationBundle.getString("id").equals(String.valueOf(sbn.getId()))) {
                originalNotificationBundle.putBundle("data", data);
              }
            }
          }
        }
      }
    }
    return aBundleList;
  }

  @ReactMethod
  public void getDisplayedNotifications(Promise promise) {
    Notifee.getInstance()
      .getDisplayedNotifications(
        (e, aBundleList) -> NotifeeReactUtils.promiseResolver(promise, e, fetchNotificationData(aBundleList)));
  }

And firebase needs to inject the data param as well, for this we added a handleIntent override for the class that extends FirebaseMessagingService (in our case we use react-native-firebase, so that would be ReactNativeFirebaseMessagingService):

private void createNotificationChannel() {
    // Same as Firebase SDK default channel name and ids
    NotificationChannel channel = new NotificationChannel("fcm_fallback_notification_channel", "Miscellaneous", NotificationManager.IMPORTANCE_HIGH);
    NotificationManager notificationManager = getSystemService(NotificationManager.class);
    notificationManager.createNotificationChannel(channel);
  }

  private int getNotificationIcon() {
    int iconResId;
    iconResId = getResources().getIdentifier("ic_notification", "drawable", getPackageName());
    if (iconResId == 0) {
      iconResId = getApplicationInfo().icon;
    }

    return iconResId;
  }

  @Override
  public void handleIntent(Intent intent) {
    Bundle extras = intent.getExtras();

    try {
      RemoteMessage message = new RemoteMessage(extras);
      RemoteMessage.Notification notificationData = message.getNotification();

      if (notificationData == null) {
        super.handleIntent(intent);
        return;
      }

      Bundle customExtras = new Bundle();
      for (Map.Entry<String, String> entry : message.getData().entrySet()) {
        customExtras.putString(entry.getKey(), entry.getValue());
      }
      createNotificationChannel();

      NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, "fcm_fallback_notification_channel")
        .setSmallIcon(getNotificationIcon())
        .setContentTitle(notificationData.getTitle())
        .setContentText(notificationData.getBody())
        .setAutoCancel(true)
        .setPriority(NotificationCompat.PRIORITY_HIGH);

      notificationBuilder.getExtras().putBundle("data", customExtras);
      NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
      // Same as tag from Firebase SDK is built
      notificationManager.notify("FCM-Notification:" + SystemClock.uptimeMillis(), 0, notificationBuilder.build());

    } catch (Exception e) {
      super.handleIntent(intent);
    }
  }
github-actions[bot] commented 1 month ago

Hello 👋, to help manage issues we automatically close stale issues.

This issue has been automatically marked as stale because it has not had activity for quite some time.Has this issue been fixed, or does it still require attention?

This issue will be closed in 15 days if no further activity occurs.

Thank you for your contributions.

mikehardy commented 4 weeks ago

Saw this come through me feed as a close - @aureosouza if you proposed PRs for this (maybe you have but I haven't seen them?) this looks like something we could integrate in react-native-firebase + notifee