customerio / customerio-reactnative

MIT License
23 stars 11 forks source link

Push notification "opened" metric is not automatically being tracked #184

Closed Joels-code closed 1 year ago

Joels-code commented 1 year ago

SDK version: 3.1.7

Environment: Production Are logs available? No, I wasn't able to get any meaningful output for this scenario.

Describe the bug The React Native SDK documentation mentions that the SDK will automatically track delivered and opened metrics. However, upon launching our first campaign that included push notifications, only delivered metrics were being tracked, and opened were not. We tested this in a test campaign by opening the push notification in multiple different app states, and the opened metric was never tracked.

To Reproduce

  1. Create a campaign with triggers that will send a push notification to a profile you have access to.
  2. Notice the delivered state is tracked.
  3. Open the push notification.
  4. Notice the push notification still has the delivered state.

Expected behavior The opened push metric is tracked automatically by the React Native SDK, as described in the documentation.

Screenshots Screenshot 2023-08-15 at 12 10 31 PM

In the screenshot below, this user confirmed that they had opened each of those push notifications. Screenshot 2023-08-15 at 12 11 45 PM

Additional context Link to the campaign: https://fly.customer.io/workspaces/101782/journeys/campaigns/200/overview?channels=email-twilio-push-webhook-slack-in_app

levibostian commented 1 year ago

Sorry to hear you're having issues with opened metrics tracking. Let me see if I can help!

From reviewing the React Native SDK docs for push metrics setup, could you share what method you have decided to go with for installing opened push metrics? Javascript functions? Native iOS? Are you using another library for handling push notifications such as expo-notifications or fcm?

My guess is that there is a missing step with installing the SDK in your app or the SDK is not integrated with a 3rd party library correctly. Hearing more information in regards to how you have setup the SDK in your app would help determine this. Could you, please, review the React Native SDK docs for push metrics setup and share code snippets from your app that correspond to what is in the docs?

We may ask for code snippets or reviewing the campaign that you linked in the issue. A public GitHub issue may not be the best place to discuss that so at some point we may need to transition over to sending an email to win@customer.io to further help you out.

Joels-code commented 1 year ago

We have gone with neither method. The React Native SDK docs for push metrics says

the SDK automatically tracks opened and delivered events for push notifications originating from Customer.io

So I did not think that there were additional steps to track the opened metric.

Is this a mistake in the documentation?

levibostian commented 1 year ago

:facepalm: Sorry about that. You are correct. My eyes glanced over the 'Did you already set up rich push?' section for some reason. My apologies.

opened metrics could be automatically setup after following the push setup instructions, yes. Here are some more details instructions that could help to debug this issue.

opened metrics in iOS are recorded by 1 specific call to the SDK. Here is a screenshot of that function: CleanShot 2023-08-16 at 08 55 16@2x

Note: Adding this function to your app is part of the push setup instructions that you referred to.

If you are not seeing opened metrics get recorded but you are seeing delivered metrics getting recorded, my guess is that function in the screenshot above is not being called.

There are 2 common reasons for this function not being called:

  1. Code in AppDelegate is missing or not correct. See screenshot below to confirm that this function exists inside of your AppDelegate file and that the function is calling the pnHandlerObj object. CleanShot 2023-08-16 at 08 58 09@2x

In order for this function in your AppDelegate to be called, you also need to make sure to include this in your AppDelegate:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
  center.delegate = self; 
}
  1. If your app is using multiple SDKs that handle push notifications, such as expo-notifications, then those SDKs can interfere with the Customer.io SDK. Only 1 SDK can handle when a push notification gets clicked. You may have a SDK that is overriding the Customer.io SDK for handling a push notification getting clicked. If that is the case, you will want to use one of the javascript functions in the React Native SDK when the other SDK tells you that the push was clicked. We include in our docs a code sample for expo-notifications if that's helpful for you.

I hope this helps! Ask if you have more questions.

Joels-code commented 1 year ago

@levibostian Thankyou so much for your help and detailed response.

The issue was that we were using react-native-push-notifications to handle fcm notifications, which is mentioned in the docs but I got confused by the "automatic" functionality mentioned lol. So adding the javascript function to track opened metrics to our notification handler did the trick for iOS devices, but it isn't working on Android for some reason. Below is how we handle a notification being opened in the index.js file of our rn project.

onNotification: function (notification) {
    const { data, userInteraction, finish } = notification;
    if (userInteraction) {
      const event = {
        url: data?.uri || data.mobile_app_url || data?.link || data?.CIO?.push?.link,
        type: data?.type,
        content_type: data?.content_type,
        content_id: data?.content_id,
        threadTitle: data?.threadTitle,
        threadId: parseInt(data?.threadId, 10),
        commentId: parseInt(data?.commentId, 10),
      };
      CustomerIO.pushMessaging().trackNotificationResponseReceived(data); // Added this today, works on iOS, doesn't on Android
      handleOpenUrl(event);
    } else if (IS_IOS) {
      showNotification(notification);
    }
    if (IS_IOS) {
      finish(PushNotificationIOS?.FetchResult?.NoData);
    } else {
      finish();
    }
  }

I checked the android logs, and the TrackPushMetric event is never called when I open the notification. It works on iOS, so I am not sure whats going on here!

I appreciate your quick responses!

Unrelated request below...

While I have you here, what do you think of adding priority options to the push notification payloads? Currently, push notifications on Android don't get through to the user if the app is closed (not in background). Adding android priority to the message payload like so

{
  "message": {
    "data": {
      "body": "",
      "title": ""
    },
    "notification": {
      "body": ""
    },
    "android":{
      "priority": "high"
    }
  }
}

allows us to deliver messages to users who have the app closed. I'm wondering if the Customer IO team could add an option to the Push notification composer to include message priority in the payload? https://firebase.google.com/docs/cloud-messaging/concept-options#setting-the-priority-of-a-message

levibostian commented 1 year ago

Yay! Glad you are starting to see some success.

Thanks for all of the details about your interpretation of our docs and the code snippets. I'll make some changes to our docs around "automatic" to try and avoid this confusion for others.

CustomerIO.pushMessaging().trackNotificationResponseReceived(data); // Added this today, works on iOS, doesn't on Android This function call is purposely ignored on Android at the moment because Android opened metrics behaves differently then on iOS. On Android, opened metrics should be tracked without needing a CustomerIO.pushMessaging().trackNotificationResponseReceived call. Send some details on behavior you're seeing on Android if you're not seeing opened metrics being reported automatically.

levibostian commented 1 year ago

I greatly appreciate you sharing your request for Android notification priority.

I added a note to our internal feature request ticket that you asked about this. Unfortunately, with feature requests, I do not have a timeline that I can share with you on when we can have this delivered. We have had this feature requested before so our team is aware of it.

Thanks again for sharing that with us.

Joels-code commented 1 year ago

@levibostian Thankyou! I appreciate you intaking my request :)

Regarding the opened metrics on Android, I have found that the opened metric isn't tracked if the notification is a localNotification. To clarify my findings:

App is in closed state -> fcm backgroundHandler -> Android OS handles notification -> push opened metrics are tracked

App is in a background state -> fcm backgroundHandler -> Android OS handles notification -> push opened metrics are tracked

App is in a foreground state -> fcm onMessage -> We display a localNotification -> push opened metrics are NOT tracked

Below is our code for handling notifications when the app is in the foreground:

PushNotification.configure({
  requestPermissions: false,
  popInitialNotification: true,
  permissions: { alert: true, sound: true },
  onNotification: function (notification) {
    const { data, userInteraction, finish } = notification;
    if (userInteraction) {
      const event = {
        url: data?.uri || data.mobile_app_url || data?.link || data?.CIO?.push?.link,
        type: data?.type,
        content_type: data?.content_type,
        content_id: data?.content_id,
        threadTitle: data?.threadTitle,
        threadId: parseInt(data?.threadId, 10),
        commentId: parseInt(data?.commentId, 10),
      };
      if (IS_IOS) {
        CustomerIO.pushMessaging().trackNotificationResponseReceived(data);
      }
      handleOpenUrl(event);
    } else if (IS_IOS) {
      showNotification(notification);
    }
    if (IS_IOS) {
      finish(PushNotificationIOS?.FetchResult?.NoData);
    } else {
      finish();
    }
  },
});

messaging().setBackgroundMessageHandler(async notification => {
  // Andorid system handles push notification if there is a notification key in payload.
  // This prevents a double push notification when the app is in a background state.
  if (!IS_IOS && 'notification' in notification) {
    return;
  }
  showNotification(notification);
});

messaging().onMessage(notification => {
  showNotification({
    message: notification.notification?.body,
    title: notification.notification?.title,
    data: notification.data,
  });
});

showNotification = notification => {
  const notif = IS_IOS
    ? {
        message: notification?.message,
        title: notification?.title,
        picture: notification?.data?.CIO?.push?.image,
        data: notification?.data,
      }
    : {
        message: notification?.data?.body || notification?.message,
        title: notification?.title || notification?.data?.title,
        picture: notification?.data?.image,
        data: notification?.data,
      };
  PushNotification.localNotification({
    smallIcon: 'ic_notification',
    color: MUSORA_BLACK,
    channelId: 'musora-app-channel',
    message: notif.message,
    title: notif.title,
    picture: notif.picture,
    data: notif.data,
    userInfo: notif.data,
  });
};

To reiterate, push notification opened metrics are not tracked when the notification is handled by messaging().onMessage() and only on Android. Would you happen to have any insight as to why this is? Can we change our code to get this working?

I appreciate your consistent cooperation in trying to get this figured out!

P.S. - For some reason I was able to receive push notifications today when in a closed state, where as yesterday I got nothing. Strange!

levibostian commented 1 year ago

Could you explain localNotification better for me? By local I think you're suggesting that your app is composing and displaying a notification in the Android system tray, not a push composed by Customer.io (a remote push notification, not local)?

You should be seeing opened metrics be reported for push notifications sent by Customer.io no matter if your app is in foreground or background. So, I think that perhaps local notifications is the reason for you not seeing opened metrics?

Joels-code commented 1 year ago

@levibostian I believe a local notification is just a push configured from the user's device instead of received remotely. We have to display a local notification when the app is in the foreground due to how FCM works. There are some details regarding this in the Firebase push messaging docs. See "Notifications" and "Foreground state messages".

The above also explains why I could receive push notifications today when the app was closed while I couldn't yesterday; it is because if we add custom data to the push notification, the payload changes to data only instead of a notification payload, and today I tested without anything in the custom data field.

There are also details on when onMessage and setBackgroundMessageHandler are used, if that helps.

I believe this issue would be resolved if Android devices did not ignore trackNotificationResponseReceived, but I do not know if that would cause other issues lol.

levibostian commented 1 year ago

Ah, yes. Thanks for reminding me about this edge case Firebase has.

I am reaching out to a teammate for more help on this scenario. I'll respond when I hear back. Thanks for your patience and cooperation.

levibostian commented 1 year ago

Thanks again for working with us in detail with this issue. After talking with my teammates, we believe that we have a solution to share with you. We also have some ideas for how we may be able to improve the SDK for edge cases such as this one. Your details helped us in a major way, thank you!

As we have discussed, you are using messaging().onMessage() in your app already. The Customer.io SDK has a companion function that is designed to work with that function. See this example:

messaging().onMessage(async remoteMessage => {
    let handled = await CustomerIO.pushMessaging().onMessageReceived(remoteMessage)

    if (!handled) {
      // The push was not sent by Customer.io. 
      // If your app can receive push notifications from more then Customer.io, your app should process `remoteMessage` in this function. 
    }
});

CustomerIO.pushMessaging().onMessageReceived()'s job is to:

I suggest giving that function a try in your app. I hope it helps!

Joels-code commented 1 year ago

Thankyou @levibostian!

That function did the trick!

There is still the issue of rich push notifications not being displayed on Android when the app is in the quit state, but in all other app states, our opened metrics are now being tracked!

I appreciate your help and patience.

levibostian commented 1 year ago

Yay! Glad to hear it. Thanks again for all the details to help get this resolved for you quickly.

As for, "push notifications not being displayed on Android when the app is in the quit state", I have mentioned your request in our internal feature request ticket. Our team will prioritize it from there.

Have an awesome rest of your week.