Open danny88dam opened 1 month ago
I haven't taken a closer look, but are you using latest expo-notifications
package? In 0.28.13 there were some android fixes.
Yep, just today I updated the libraries and rebuilt the app again with eas build ---clear-cache command.
Nothing seems to be changed, here my package.json
{ "name": "myApp", "version": "1.0.0", "main": "node_modules/expo/AppEntry.js", "scripts": { "start": "expo start", "android": "expo start --android", "ios": "expo start --ios", "web": "expo start --web", "ts:check": "tsc", "postinstall": "patch-package" }, "dependencies": { "@allboatsrise/expo-marketingcloudsdk": "^51.0.1", "@expo-google-fonts/inter": "^0.2.3", "@expo/html-elements": "^0.9.1", "@expo/metro-runtime": "~3.2.1", "@react-native-async-storage/async-storage": "1.23.1", "@react-native-community/datetimepicker": "8.0.1", "@react-native-picker/picker": "2.7.5", "@react-navigation/bottom-tabs": "^6.5.14", "@react-navigation/native": "^6.1.12", "@react-navigation/native-stack": "^6.9.20", "@react-navigation/stack": "^6.3.23", "@reduxjs/toolkit": "^2.2.1", "@rneui/base": "^4.0.0-rc.7", "@rneui/themed": "^4.0.0-rc.8", "axios": "^1.6.7", "dotenv": "^16.4.5", "expo": "~51.0.24", "expo-blur": "~13.0.2", "expo-constants": "~16.0.2", "expo-dev-client": "~4.0.21", "expo-device": "~6.0.2", "expo-document-picker": "~12.0.2", "expo-file-system": "~17.0.1", "expo-font": "~12.0.5", "expo-image": "~1.12.13", "expo-linear-gradient": "~13.0.2", "expo-linking": "~6.3.1", "expo-localization": "~15.0.3", "expo-notifications": "~0.28.14", "expo-secure-store": "~13.0.2", "expo-status-bar": "~1.12.1", "expo-updates": "~0.25.21", "expo-web-browser": "~13.0.3", "formik": "^2.4.5", "lodash": "^4.17.21", "patch-package": "^8.0.0", "react": "18.2.0", "react-dom": "18.2.0", "react-native": "0.74.3", "react-native-calendars": "^1.1304.1", "react-native-elements": "^3.4.3", "react-native-gesture-handler": "~2.16.1", "react-native-keyboard-aware-scroll-view": "^0.9.5", "react-native-otp-entry": "^1.6.1", "react-native-pager-view": "6.3.0", "react-native-reanimated": "~3.10.1", "react-native-reanimated-carousel": "^3.5.1", "react-native-render-html": "^6.3.4", "react-native-safe-area-context": "4.10.5", "react-native-screens": "3.31.1", "react-native-tab-view": "^3.5.2", "react-native-vimeo-iframe": "^1.2.1", "react-native-web": "~0.19.6", "react-native-webview": "13.8.6", "react-redux": "^9.1.0", "redux": "^5.0.1", "redux-persist": "^6.0.0", "reselect": "^5.1.0", "text-clipper": "^2.2.0", "yup": "^1.3.3", "zod": "^3.23.8" }, "devDependencies": { "@babel/core": "^7.20.0", "@types/lodash": "^4.14.202", "@types/react": "~18.2.79", "expo-module-scripts": "^3.5.2", "typescript": "~5.3.3" }, "private": true }
I made other test to better understand and delimit the scenario.
I made a campaign via Firebase console and sent push notification to my android device. In this case I correctly received the notification data in app, here the json
{
"notification": {
"request": {
"trigger": {
"remoteMessage": {
"originalPriority": 1,
"sentTime": 1722952149049,
"notification": {
"usesDefaultVibrateSettings": false,
"color": null,
"channelId": null,
"visibility": null,
"sound": null,
"tag": "campaign_collapse_key_4954627017329097723",
"bodyLocalizationArgs": null,
"imageUrl": *********************,
"title": "test 7",
"ticker": null,
"eventTime": null,
"body": "test 7",
"titleLocalizationKey": null,
"notificationPriority": null,
"icon": null,
"usesDefaultLightSettings": false,
"sticky": false,
"link": null,
"titleLocalizationArgs": null,
"bodyLocalizationKey": null,
"usesDefaultSound": false,
"clickAction": null,
"localOnly": false,
"lightSettings": null,
"notificationCount": null
},
"data": {
"key1": "value1"
},
"to": null,
"ttl": 2419200,
"collapseKey": *********************,
"messageType": null,
"priority": 1,
"from": *********************,
"messageId": "0:1722952154045214%e874ac06e874ac06"
},
"channelId": null,
"type": "push"
},
"content": {
"title": null,
"badge": null,
"autoDismiss": true,
"data": {
"key1": "value1"
},
"body": null,
"sound": "default",
"sticky": false,
"subtitle": null
},
"identifier": "0:1722952154045214%e874ac06e874ac06"
},
"date": 1722952149049
},
"actionIdentifier": "expo.modules.notifications.actions.DEFAULT"
}
then I retried to send notification via MC and still received empty data:
{
"notification": {
"request": {
"trigger": {
"channelId": null,
"type": "push"
},
"content": {
"title": null,
"data": {
"com.salesforce.marketingcloud.notifications.EXTRA_MESSAGE": "__expo_dynamic_extension__#1"
}
},
"identifier": null
},
"date": 0
},
"actionIdentifier": "expo.modules.notifications.actions.DEFAULT"
}
Can you try the latest expo-notifications
version 0.28.15
- expo team screwed up some refactoring and I just want to make sure it's not related to this.
You can also try debugging and seeing how the data comes in here. You could just call super.onMessageReceived(remoteMessage)
and not pass it to sfmc for processing to see if you get all the data.
https://github.com/allboatsrise/expo-marketingcloudsdk/blob/23ae72d87af34df2f1442f4b988f2fffaa8bf0e8/android/src/main/java/expo/modules/marketingcloudsdk/service/ExpoFirebaseMessagingService.kt#L8-L20
Hi, yesterday I noticed the expo-notifcations new version then I updated my packeges but nothig changed.
Follow your suggestion I directly call super.onMessageReceived(remoteMessage)
in onMessageReceived
method and IT WORKS, so i now I receive something like this
"notification": {
"request": {
"trigger": {
"remoteMessage": {
"originalPriority": 2,
"sentTime": 1723015711410,
"notification": null,
"data": {
"title": "Title",
"sound": "default",
"_r": ********************,
"_mediaAlt": "Wall_BP_Image",
"alert": "Message Text\n",
"_sid": "SFMC",
"_m": "MTY1OjExNDow",
"_od": ********************,
"_mt": "1",
"_h": ********************,
"_mediaUrl": ********************,
"subtitle": "Subtitle Text"
},
"to": null,
"ttl": 2419200,
"collapseKey": null,
"messageType": null,
"priority": 2,
"from": "413196589577",
"messageId": "0:1723015711420859%e874ac06f9fd7ecd"
},
"channelId": null,
"type": "push"
},
"content": {
"title": "Title",
"badge": null,
"autoDismiss": true,
"data": {
"title": "Title",
"sound": "default",
"_r": ********************,
"_mediaAlt": "Wall_BP_Image",
"alert": "Message Text\n",
"_sid": "SFMC",
"_m": "MTY1OjExNDow",
"_od": ********************,
"_mt": "1",
"_h": ********************,
"_mediaUrl": ********************,
"subtitle": "Subtitle Text"
},
"body": null,
"sound": "custom",
"sticky": false,
"subtitle": "Subtitle Text"
},
"identifier": "0:1723015711420859%e874ac06f9fd7ecd"
},
"date": 1723015711410
},
"actionIdentifier": "expo.modules.notifications.actions.DEFAULT"
}
}
In conclution the issue related to pushMessageManager.handleMessage
method. I tried to look further by searching the official documentation, but i found nothing.
Only this link https://developer.salesforce.com/docs/marketing/mobilepush/guide/troubleshooting-android.html#troubleshoot-multiple-push-sdks talks about handleMessage
but the linked page is 404
My question now is: can this solution be considered stable? Or does it need further investigation?
I wonder if it's ok to trigger both, it.pushMessageManager.handleMessage(remoteMessage)
and super.onMessageReceived(remoteMessage)
, since I think both are needed.
I think it is a good idea to call them both. I will try it in the next build and let you know. I think the worst case scenario is that the app gets the same notification twice. However, I opened a ticket on MC support describing the scenario, I am waiting for their response and details.
[UPDATE]
I made a new build trying to call both super.onMessageReceived(remoteMessage)
and it.pushMessageManager.handleMessage(remoteMessage)
.
As expected, I get the same push notification twice.
I also noticed another thing, I set up sending the notification with an image, when Android receives the (duplicated) notifications only the first one that is handled by it.pushMessageManager.handleMessage(remoteMessage)
is displayed correctly in preview with the image and the body, the last one only shows only title.
I think title is standard fields handled in same way by both MC and Firebase, instead the body and the image fields are apparently sent as a custom fields from MC MobilePush service, so only if the notification was handled by MC's it.pushMessageManager.handleMessage(remoteMessage)
method they were rearranged somehow to be correctlty previewed by Android.
Thus these type of rearrangements seems to be one of the features of it.pushMessageManager.handleMessage(remoteMessage)
Does it show the notification twice only when the app is running? Or also when the application is not running (killed) and the notification is received, it is still shown twice?
In all cases app is runnign foreground, app is runnig background and app is killed the push notification shows twice
I'm in the process of moving from a bare react native project to expo managed. I'm running into the same issue mentioned by @danny88dam. I also got the same result when I called super.onMessageReceived(remoteMessage)
for Marketing Cloud pushes as well. I get two pushes. One push has the extra parameters but doesn't have the title and other information filled in.
I'm wondering if there have been any updates on this issue as I continue digging in to understand how this library works and see if I can figure out a way around this issue. Thanks.
I've been having a similar issue, where the push payload on android is null and the data
field is encoded on a weird way, but the push is rendered fine on the device.:
{
"actionIdentifier": "expo.modules.notifications.actions.DEFAULT",
"notification": {
"date": 0,
"request": {
"identifier": null,
"content": {
data: // see bellow,
"title": null
},
"trigger": {
"type": "push",
"channelId": null
}
}
}
}
"data": {
"com.salesforce.marketingcloud.notifications.EXTRA_MESSAGE": {
"0": 14,
"1": 0,
"2": 0,
"3": 0,
"4": 77,
"5": 0,
"6": 106,
"7": 0,
"8": 107,
"9": 0,
"10": 122,
"11": 0,
"12": 77,
"13": 0,
"14": 122,
"15": 0,
"16": 111,
"17": 0,
"18": 120,
"19": 0,
"20": 77,
"21": 0,
"22": 84,
"23": 0,
"24": 81,
"25": 0,
"26": 54,
"27": 0,
"28": 77,
"29": 0,
"30": 65,
"31": 0,
"32": 0,
"33": 0,
"34": 0,
"35": 0,
"36": 36,
"37": 0,
"38": 0,
"39": 0,
"40": 102,
"41": 0,
"42": 48,
"43": 0,
"44": 50,
"45": 0,
"46": 98,
"47": 0,
"48": 57,
"49": 0,
"50": 52,
"51": 0,
"52": 50,
"53": 0,
"54": 98,
"55": 0,
"56": 45,
"57": 0,
"58": 48,
"59": 0,
"60": 51,
"61": 0,
"62": 97,
"63": 0,
"64": 55,
"65": 0,
"66": 45,
"67": 0,
"68": 52,
"69": 0,
"70": 52,
"71": 0,
"72": 97,
"73": 0,
"74": 50,
"75": 0,
"76": 45,
"77": 0,
"78": 57,
"79": 0,
"80": 98,
"81": 0,
"82": 55,
"83": 0,
"84": 53,
"85": 0,
"86": 45,
"87": 0,
"88": 57,
"89": 0,
"90": 98,
"91": 0,
"92": 54,
"93": 0,
"94": 54,
"95": 0,
"96": 55,
"97": 0,
"98": 97,
"99": 0,
"100": 99,
"101": 0,
"102": 49,
"103": 0,
"104": 98,
"105": 0,
"106": 52,
"107": 0,
"108": 49,
"109": 0,
"110": 56,
"111": 0,
"112": 0,
"113": 0,
"114": 0,
"115": 0,
"116": 0,
"117": 0,
"118": 0,
"119": 0,
"120": 50,
"121": 0,
"122": 0,
"123": 0,
"124": 67,
"125": 0,
"126": 111,
"127": 0,
"128": 110,
"129": 0,
"130": 103,
"131": 0,
"132": 114,
"133": 0,
"134": 97,
"135": 0,
"136": 116,
"137": 0,
"138": 117,
"139": 0,
"140": 108,
"141": 0,
"142": 97,
"143": 0,
"144": 116,
"145": 0,
"146": 105,
"147": 0,
"148": 111,
"149": 0,
"150": 110,
"151": 0,
"152": 115,
"153": 0,
"154": 33,
"155": 0,
"156": 32,
"157": 0,
"158": 89,
"159": 0,
"160": 111,
"161": 0,
"162": 117,
"163": 0,
"164": 114,
"165": 0,
"166": 32,
"167": 0,
"168": 77,
"169": 0,
"170": 111,
"171": 0,
"172": 98,
"173": 0,
"174": 105,
"175": 0,
"176": 108,
"177": 0,
"178": 101,
"179": 0,
"180": 80,
"181": 0,
"182": 117,
"183": 0,
"184": 115,
"185": 0,
"186": 104,
"187": 0,
"188": 32,
"189": 0,
"190": 115,
"191": 0,
"192": 101,
"193": 0,
"194": 116,
"195": 0,
"196": 117,
"197": 0,
"198": 112,
"199": 0,
"200": 32,
"201": 0,
"202": 105,
"203": 0,
"204": 115,
"205": 0,
"206": 32,
"207": 0,
"208": 119,
"209": 0,
"210": 111,
"211": 0,
"212": 114,
"213": 0,
"214": 107,
"215": 0,
"216": 105,
"217": 0,
"218": 110,
"219": 0,
"220": 103,
"221": 0,
"222": 46,
"223": 0,
"224": 0,
"225": 0,
"226": 0,
"227": 0,
"228": 4,
"229": 0,
"230": 0,
"231": 0,
"232": 78,
"233": 0,
"234": 79,
"235": 0,
"236": 78,
"237": 0,
"238": 69,
"239": 0,
"240": 0,
"241": 0,
"242": 0,
"243": 0,
"244": 255,
"245": 255,
"246": 255,
"247": 255,
"248": 20,
"249": 0,
"250": 0,
"251": 0,
"252": 77,
"253": 0,
"254": 111,
"255": 0,
"256": 98,
"257": 0,
"258": 105,
"259": 0,
"260": 108,
"261": 0,
"262": 101,
"263": 0,
"264": 80,
"265": 0,
"266": 117,
"267": 0,
"268": 115,
"269": 0,
"270": 104,
"271": 0,
"272": 32,
"273": 0,
"274": 84,
"275": 0,
"276": 101,
"277": 0,
"278": 115,
"279": 0,
"280": 116,
"281": 0,
"282": 32,
"283": 0,
"284": 83,
"285": 0,
"286": 101,
"287": 0,
"288": 110,
"289": 0,
"290": 100,
"291": 0,
"292": 0,
"293": 0,
"294": 0,
"295": 0,
"296": 255,
"297": 255,
"298": 255,
"299": 255,
"300": 5,
"301": 0,
"302": 0,
"303": 0,
"304": 79,
"305": 0,
"306": 84,
"307": 0,
"308": 72,
"309": 0,
"310": 69,
"311": 0,
"312": 82,
"313": 0,
"314": 0,
"315": 0,
"316": 4,
"317": 0,
"318": 0,
"319": 0,
"320": 80,
"321": 0,
"322": 85,
"323": 0,
"324": 83,
"325": 0,
"326": 72,
"327": 0,
"328": 0,
"329": 0,
"330": 0,
"331": 0,
"332": 255,
"333": 255,
"334": 255,
"335": 255,
"336": 255,
"337": 255,
"338": 255,
"339": 255,
"340": 255,
"341": 255,
"342": 255,
"343": 255,
"344": 0,
"345": 0,
"346": 0,
"347": 0,
"348": 255,
"349": 255,
"350": 255,
"351": 255,
"352": 1,
"353": 0,
"354": 0,
"355": 0,
"356": 7,
"357": 0,
"358": 0,
"359": 0,
"360": 2,
"361": 0,
"362": 0,
"363": 0,
"364": 95,
"365": 0,
"366": 114,
"367": 0,
"368": 0,
"369": 0,
"370": 0,
"371": 0,
"372": 36,
"373": 0,
"374": 0,
"375": 0,
"376": 102,
"377": 0,
"378": 48,
"379": 0,
"380": 50,
"381": 0,
"382": 98,
"383": 0,
"384": 57,
"385": 0,
"386": 52,
"387": 0,
"388": 50,
"389": 0,
"390": 98,
"391": 0,
"392": 45,
"393": 0,
"394": 48,
"395": 0,
"396": 51,
"397": 0,
"398": 97,
"399": 0,
"400": 55,
"401": 0,
"402": 45,
"403": 0,
"404": 52,
"405": 0,
"406": 52,
"407": 0,
"408": 97,
"409": 0,
"410": 50,
"411": 0,
"412": 45,
"413": 0,
"414": 57,
"415": 0,
"416": 98,
"417": 0,
"418": 55,
"419": 0,
"420": 53,
"421": 0,
"422": 45,
"423": 0,
"424": 57,
"425": 0,
"426": 98,
"427": 0,
"428": 54,
"429": 0,
"430": 54,
"431": 0,
"432": 55,
"433": 0,
"434": 97,
"435": 0,
"436": 99,
"437": 0,
"438": 49,
"439": 0,
"440": 98,
"441": 0,
"442": 52,
"443": 0,
"444": 49,
"445": 0,
"446": 56,
"447": 0,
"448": 0,
"449": 0,
"450": 0,
"451": 0,
"452": 4,
"453": 0,
"454": 0,
"455": 0,
"456": 95,
"457": 0,
"458": 115,
"459": 0,
"460": 105,
"461": 0,
"462": 100,
"463": 0,
"464": 0,
"465": 0,
"466": 0,
"467": 0,
"468": 4,
"469": 0,
"470": 0,
"471": 0,
"472": 83,
"473": 0,
"474": 70,
"475": 0,
"476": 77,
"477": 0,
"478": 67,
"479": 0,
"480": 0,
"481": 0,
"482": 0,
"483": 0,
"484": 5,
"485": 0,
"486": 0,
"487": 0,
"488": 97,
"489": 0,
"490": 108,
"491": 0,
"492": 101,
"493": 0,
"494": 114,
"495": 0,
"496": 116,
"497": 0,
"498": 0,
"499": 0,
"500": 50,
"501": 0,
"502": 0,
"503": 0,
"504": 67,
"505": 0,
"506": 111,
"507": 0,
"508": 110,
"509": 0,
"510": 103,
"511": 0,
"512": 114,
"513": 0,
"514": 97,
"515": 0,
"516": 116,
"517": 0,
"518": 117,
"519": 0,
"520": 108,
"521": 0,
"522": 97,
"523": 0,
"524": 116,
"525": 0,
"526": 105,
"527": 0,
"528": 111,
"529": 0,
"530": 110,
"531": 0,
"532": 115,
"533": 0,
"534": 33,
"535": 0,
"536": 32,
"537": 0,
"538": 89,
"539": 0,
"540": 111,
"541": 0,
"542": 117,
"543": 0,
"544": 114,
"545": 0,
"546": 32,
"547": 0,
"548": 77,
"549": 0,
"550": 111,
"551": 0,
"552": 98,
"553": 0,
"554": 105,
"555": 0,
"556": 108,
"557": 0,
"558": 101,
"559": 0,
"560": 80,
"561": 0,
"562": 117,
"563": 0,
"564": 115,
"565": 0,
"566": 104,
"567": 0,
"568": 32,
"569": 0,
"570": 115,
"571": 0,
"572": 101,
"573": 0,
"574": 116,
"575": 0,
"576": 117,
"577": 0,
"578": 112,
"579": 0,
"580": 32,
"581": 0,
"582": 105,
"583": 0,
"584": 115,
"585": 0,
"586": 32,
"587": 0,
"588": 119,
"589": 0,
"590": 111,
"591": 0,
"592": 114,
"593": 0,
"594": 107,
"595": 0,
"596": 105,
"597": 0,
"598": 110,
"599": 0,
"600": 103,
"601": 0,
"602": 46,
"603": 0,
"604": 0,
"605": 0,
"606": 0,
"607": 0,
"608": 3,
"609": 0,
"610": 0,
"611": 0,
"612": 95,
"613": 0,
"614": 109,
"615": 0,
"616": 116,
"617": 0,
"618": 0,
"619": 0,
"620": 1,
"621": 0,
"622": 0,
"623": 0,
"624": 49,
"625": 0,
"626": 0,
"627": 0,
"628": 2,
"629": 0,
"630": 0,
"631": 0,
"632": 95,
"633": 0,
"634": 104,
"635": 0,
"636": 0,
"637": 0,
"638": 0,
"639": 0,
"640": 28,
"641": 0,
"642": 0,
"643": 0,
"644": 88,
"645": 0,
"646": 86,
"647": 0,
"648": 104,
"649": 0,
"650": 117,
"651": 0,
"652": 80,
"653": 0,
"654": 87,
"655": 0,
"656": 51,
"657": 0,
"658": 103,
"659": 0,
"660": 110,
"661": 0,
"662": 105,
"663": 0,
"664": 88,
"665": 0,
"666": 85,
"667": 0,
"668": 81,
"669": 0,
"670": 114,
"671": 0,
"672": 113,
"673": 0,
"674": 47,
"675": 0,
"676": 51,
"677": 0,
"678": 80,
"679": 0,
"680": 99,
"681": 0,
"682": 85,
"683": 0,
"684": 78,
"685": 0,
"686": 81,
"687": 0,
"688": 65,
"689": 0,
"690": 65,
"691": 0,
"692": 65,
"693": 0,
"694": 65,
"695": 0,
"696": 65,
"697": 0,
"698": 65,
"699": 0,
"700": 0,
"701": 0,
"702": 0,
"703": 0,
"704": 5,
"705": 0,
"706": 0,
"707": 0,
"708": 116,
"709": 0,
"710": 105,
"711": 0,
"712": 116,
"713": 0,
"714": 108,
"715": 0,
"716": 101,
"717": 0,
"718": 0,
"719": 0,
"720": 20,
"721": 0,
"722": 0,
"723": 0,
"724": 77,
"725": 0,
"726": 111,
"727": 0,
"728": 98,
"729": 0,
"730": 105,
"731": 0,
"732": 108,
"733": 0,
"734": 101,
"735": 0,
"736": 80,
"737": 0,
"738": 117,
"739": 0,
"740": 115,
"741": 0,
"742": 104,
"743": 0,
"744": 32,
"745": 0,
"746": 84,
"747": 0,
"748": 101,
"749": 0,
"750": 115,
"751": 0,
"752": 116,
"753": 0,
"754": 32,
"755": 0,
"756": 83,
"757": 0,
"758": 101,
"759": 0,
"760": 110,
"761": 0,
"762": 100,
"763": 0,
"764": 0,
"765": 0,
"766": 0,
"767": 0,
"768": 2,
"769": 0,
"770": 0,
"771": 0,
"772": 95,
"773": 0,
"774": 109,
"775": 0,
"776": 0,
"777": 0,
"778": 0,
"779": 0,
"780": 14,
"781": 0,
"782": 0,
"783": 0,
"784": 77,
"785": 0,
"786": 106,
"787": 0,
"788": 107,
"789": 0,
"790": 122,
"791": 0,
"792": 77,
"793": 0,
"794": 122,
"795": 0,
"796": 111,
"797": 0,
"798": 120,
"799": 0,
"800": 77,
"801": 0,
"802": 84,
"803": 0,
"804": 81,
"805": 0,
"806": 54,
"807": 0,
"808": 77,
"809": 0,
"810": 65,
"811": 0,
"812": 0,
"813": 0,
"814": 0,
"815": 0,
"816": 255,
"817": 255,
"818": 255,
"819": 255,
"820": 10,
"821": 0,
"822": 0,
"823": 0
}
}
package.json
"dependencies": {
"@allboatsrise/expo-marketingcloudsdk": "51.0.4",
"expo": "^51.0.26",
"expo-notifications": "~0.28.15",
"react": "18.2.0",
"react-native": "0.74.5",
"zod": "3.21.4"
Notifications.getLastNotificationResponseAsync().then(async (response) => {
if (response?.notification) {
onOpened(response.notification);
} else {
// error handling
}
});
Looks like it is this issue in the expo-notifications library https://github.com/expo/expo/issues/30868. @andrejpavlovic has already commented on the issue.
Looks like it is this issue in the expo-notifications library expo/expo#30868. @andrejpavlovic has already commented on the issue.
Awesome! Will try the patch, @spenceps thanks for forwarding me to that thread!
Awesome! Will try the patch, @spenceps thanks for forwarding me to that thread!
@andrescalco-borrowell - The patch didn't work for me. (maybe I did it wrong though) Have you tried it yet?
Hi @andrescalco-borrowell, I tried out the reported patch here and it doesn't seem to work neither for me.
Unfortunately it did not fix the issue for me =(
The issue turns out to be that Marketing Cloud SDK creates the launch Intent in a way that the custom keys aren't included in the normal way. I found a way around this by overriding the creation of intent as shown here.
I haven't fully tested this yet but it does at least work for the basic case.
This is done in ExpoMarketinCloudSdkApplicationLifecycleListener.kt
private fun getNotificationOptions(context: Context): NotificationCustomizationOptions {
return NotificationCustomizationOptions.create(
R.drawable.notification_icon,
{ context, notificationMessage ->
val requestCode = Random.Default.nextInt()
val url = notificationMessage.url
val payload = notificationMessage.customKeys
val intent = context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply {
// Add custom keys to the intent
payload.forEach { (key, value) ->
putExtra(key, value)
}
if (!url.isNullOrEmpty()) {
putExtra("url", url)
}
}
// Return a PendingIntent with the Intent that includes the custom keys
PendingIntent.getActivity(
context,
requestCode,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
},
{ context, notificationMessage ->
if (notificationMessage.url.isNullOrEmpty()) {
NotificationManager.createDefaultNotificationChannel(context)
} else {
"UrlNotification"
}
}
)
}
In the configuration call the above function instead of just providing an image.
SFMCSdk.configure(application, SFMCSdkModuleConfig.build {
pushModuleConfig = MarketingCloudConfig.builder().apply {
setApplicationId(getAppId(application))
setAccessToken(getAccessToken(application))
setAnalyticsEnabled(getAnalyticsEnabled(application))
setMarketingCloudServerUrl(getServerUrl(application))
setDelayRegistrationUntilContactKeyIsSet(getDelayRegistrationUntilContactKeyIsSet(application))
if(getSenderId(application) != "") setSenderId(getSenderId(application))
setInboxEnabled(getInboxEnabled(application))
setMarkMessageReadOnInboxNotificationOpen(getMarkMessageReadOnInboxNotificationOpen(application))
setNotificationCustomizationOptions(
getNotificationOptions(application) // HERE IS THE REPLACEMENT
)
}.build(application)
}) { initStatus ->
// TODO handle initialization status
Log.e("SMFCSdk: initStatus", initStatus.toString())
}
getNotificationOptions(application)
Hi @spenceps I tried out your solution, but I don't get push notification anymore.
Maybe I made some mistake in the implementation of your code, below are the parts I had to add
import android.app.PendingIntent
import java.util.Random
import com.salesforce.marketingcloud.notifications.NotificationManager
I had to change this val requestCode = Random.Default.nextInt()
to this val requestCode = Random().nextInt()
, because eas build gives me fatal error on Random.Default.
Finally I called the getNotificationOptions function within MarketingCloudConfig.builder().apply as suggested.
Here the complete code of ExpoMarketingCloudSdkApplicationLifecycleListener
class
package expo.modules.marketingcloudsdk
import android.app.Application
import android.content.Context
import android.util.Log
import com.salesforce.marketingcloud.MCLogListener
import com.salesforce.marketingcloud.MarketingCloudConfig
import com.salesforce.marketingcloud.MarketingCloudSdk
import com.salesforce.marketingcloud.notifications.NotificationCustomizationOptions
import com.salesforce.marketingcloud.notifications.NotificationManager
import com.salesforce.marketingcloud.sfmcsdk.BuildConfig
import com.salesforce.marketingcloud.sfmcsdk.SFMCSdk
import com.salesforce.marketingcloud.sfmcsdk.SFMCSdkModuleConfig
import com.salesforce.marketingcloud.sfmcsdk.components.logging.LogLevel
import com.salesforce.marketingcloud.sfmcsdk.components.logging.LogListener
import expo.modules.core.interfaces.ApplicationLifecycleListener
import android.app.PendingIntent
import java.util.Random
class ExpoMarketingCloudSdkApplicationLifecycleListener : ApplicationLifecycleListener {
override fun onCreate(application: Application) {
// Initialize logging _before_ initializing the SDK to avoid losing valuable debugging information.
if(getDebug(application)) {
SFMCSdk.setLogging(LogLevel.DEBUG, LogListener.AndroidLogger())
MarketingCloudSdk.setLogLevel(MCLogListener.VERBOSE)
MarketingCloudSdk.setLogListener(MCLogListener.AndroidLogListener())
SFMCSdk.requestSdk { sdk ->
sdk.mp { push ->
push.registrationManager.registerForRegistrationEvents {
// Log the registration on successful sends to the MC
Log.i("~#SFMC-expo", "Registration: $it")
}
}
}
}
// Configure Salesforce Marketing Cloud SDK
SFMCSdk.configure(application, SFMCSdkModuleConfig.build {
pushModuleConfig = MarketingCloudConfig.builder().apply {
setApplicationId(getAppId(application))
setAccessToken(getAccessToken(application))
setAnalyticsEnabled(getAnalyticsEnabled(application))
setMarketingCloudServerUrl(getServerUrl(application))
setDelayRegistrationUntilContactKeyIsSet(getDelayRegistrationUntilContactKeyIsSet(application))
if(getSenderId(application) != "") setSenderId(getSenderId(application))
setInboxEnabled(getInboxEnabled(application))
setMarkMessageReadOnInboxNotificationOpen(getMarkMessageReadOnInboxNotificationOpen(application))
setNotificationCustomizationOptions(getNotificationOptions(application))
}.build(application)
}) { initStatus ->
// TODO handle initialization status
Log.e("SMFCSdk: initStatus", initStatus.toString())
}
}
private fun getDebug(context: Context): Boolean = context.resources.getString(R.string.expo_marketingcloudsdk_debug) == "true"
private fun getAppId(context: Context): String = context.resources.getString(R.string.expo_marketingcloudsdk_app_id)
private fun getAccessToken(context: Context): String = context.resources.getString(R.string.expo_marketingcloudsdk_access_token)
private fun getServerUrl(context: Context): String = context.resources.getString(R.string.expo_marketingcloudsdk_server_url)
private fun getSenderId(context: Context): String = context.resources.getString(R.string.expo_marketingcloudsdk_sender_id)
private fun getAnalyticsEnabled(context: Context): Boolean = context.resources.getString(R.string.expo_marketingcloudsdk_analytics_enabled) == "true"
private fun getDelayRegistrationUntilContactKeyIsSet(context: Context): Boolean = context.resources.getString(R.string.expo_marketingcloudsdk_delay_registration_until_contact_key_is_set) == "true"
private fun getInboxEnabled(context: Context): Boolean = context.resources.getString(R.string.expo_marketingcloudsdk_inbox_enabled) == "true"
private fun getMarkMessageReadOnInboxNotificationOpen(context: Context): Boolean = context.resources.getString(R.string.expo_marketingcloudsdk_mark_message_read_on_inbox_notification_open) == "true"
private fun getNotificationOptions(context: Context): NotificationCustomizationOptions {
return NotificationCustomizationOptions.create(
R.drawable.notification_icon,
{ context, notificationMessage ->
val requestCode = Random().nextInt()
val url = notificationMessage.url
val payload = notificationMessage.customKeys
val intent = context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply {
// Add custom keys to the intent
payload.forEach { (key, value) ->
putExtra(key, value)
}
if (!url.isNullOrEmpty()) {
putExtra("url", url)
}
}
// Return a PendingIntent with the Intent that includes the custom keys
PendingIntent.getActivity(
context,
requestCode,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
},
{ context, notificationMessage ->
if (notificationMessage.url.isNullOrEmpty()) {
NotificationManager.createDefaultNotificationChannel(context)
} else {
"UrlNotification"
}
}
)
}
}
Please tell me if there is something wrong.
I had to change this
val requestCode = Random.Default.nextInt()
to thisval requestCode = Random().nextInt()
, because eas build gives me fatal error on Random.Default.
@danny88dam I used import kotlin.random.Random
for the random number. I don't know if that is the issue though.
Here is the full file:
package expo.modules.marketingcloudsdk
import android.app.Application
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.util.Log
import com.salesforce.marketingcloud.MCLogListener
import com.salesforce.marketingcloud.MarketingCloudConfig
import com.salesforce.marketingcloud.MarketingCloudSdk
import com.salesforce.marketingcloud.notifications.NotificationCustomizationOptions
import com.salesforce.marketingcloud.notifications.NotificationManager
import com.salesforce.marketingcloud.sfmcsdk.SFMCSdk
import com.salesforce.marketingcloud.sfmcsdk.SFMCSdkModuleConfig
import com.salesforce.marketingcloud.sfmcsdk.components.logging.LogLevel
import com.salesforce.marketingcloud.sfmcsdk.components.logging.LogListener
import expo.modules.core.interfaces.ApplicationLifecycleListener
import kotlin.random.Random
class ExpoMarketingCloudSdkApplicationLifecycleListener : ApplicationLifecycleListener {
override fun onCreate(application: Application) {
// Initialize logging _before_ initializing the SDK to avoid losing valuable debugging information.
if(getDebug(application)) {
SFMCSdk.setLogging(LogLevel.DEBUG, LogListener.AndroidLogger())
MarketingCloudSdk.setLogLevel(MCLogListener.VERBOSE)
MarketingCloudSdk.setLogListener(MCLogListener.AndroidLogListener())
SFMCSdk.requestSdk { sdk ->
sdk.mp { push ->
push.registrationManager.registerForRegistrationEvents {
// Log the registration on successful sends to the MC
Log.i("~#SFMC-expo", "Registration: $it")
}
}
}
}
// Configure Salesforce Marketing Cloud SDK
SFMCSdk.configure(application, SFMCSdkModuleConfig.build {
pushModuleConfig = MarketingCloudConfig.builder().apply {
setApplicationId(getAppId(application))
setAccessToken(getAccessToken(application))
setAnalyticsEnabled(getAnalyticsEnabled(application))
setMarketingCloudServerUrl(getServerUrl(application))
setDelayRegistrationUntilContactKeyIsSet(getDelayRegistrationUntilContactKeyIsSet(application))
if(getSenderId(application) != "") setSenderId(getSenderId(application))
setInboxEnabled(getInboxEnabled(application))
setMarkMessageReadOnInboxNotificationOpen(getMarkMessageReadOnInboxNotificationOpen(application))
setNotificationCustomizationOptions(
getNotificationOptions(application)
)
}.build(application)
}) { initStatus ->
// TODO handle initialization status
Log.e("SMFCSdk: initStatus", initStatus.toString())
}
}
private fun getNotificationOptions(context: Context): NotificationCustomizationOptions {
return NotificationCustomizationOptions.create(
R.drawable.notification_icon,
{ context, notificationMessage ->
val requestCode = Random.Default.nextInt()
val url = notificationMessage.url
val payload = notificationMessage.customKeys
val intent = context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply {
// Add custom keys to the intent
payload.forEach { (key, value) ->
putExtra(key, value)
}
if (!url.isNullOrEmpty()) {
putExtra("url", url)
}
}
// Return a PendingIntent with the Intent that includes the custom keys
PendingIntent.getActivity(
context,
requestCode,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
},
{ context, notificationMessage ->
if (notificationMessage.url.isNullOrEmpty()) {
NotificationManager.createDefaultNotificationChannel(context)
} else {
"UrlNotification"
}
}
)
}
private fun getDebug(context: Context): Boolean = context.resources.getString(R.string.expo_marketingcloudsdk_debug) == "true"
private fun getAppId(context: Context): String = context.resources.getString(R.string.expo_marketingcloudsdk_app_id)
private fun getAccessToken(context: Context): String = context.resources.getString(R.string.expo_marketingcloudsdk_access_token)
private fun getServerUrl(context: Context): String = context.resources.getString(R.string.expo_marketingcloudsdk_server_url)
private fun getSenderId(context: Context): String = context.resources.getString(R.string.expo_marketingcloudsdk_sender_id)
private fun getAnalyticsEnabled(context: Context): Boolean = context.resources.getString(R.string.expo_marketingcloudsdk_analytics_enabled) == "true"
private fun getDelayRegistrationUntilContactKeyIsSet(context: Context): Boolean = context.resources.getString(R.string.expo_marketingcloudsdk_delay_registration_until_contact_key_is_set) == "true"
private fun getInboxEnabled(context: Context): Boolean = context.resources.getString(R.string.expo_marketingcloudsdk_inbox_enabled) == "true"
private fun getMarkMessageReadOnInboxNotificationOpen(context: Context): Boolean = context.resources.getString(R.string.expo_marketingcloudsdk_mark_message_read_on_inbox_notification_open) == "true"
}
Hi @spenceps I tryed your version and it seems that the system correctly receives the notification, but addNotificationResponseReceivedListener doesn't trigger when the user taps on the notification, instead when the user tap on the notification handled vai firebase/expo method onMessageReceived addNotificationResponseReceivedListener seems to trrigger regularly.
I also discover that sfmc api handles the opt-out of push noticiation in app, so when the push notification has been sent it wil be delivered to all registered devices, the app of each device is going to check via sfmc sdk whether the user has otped out and if so, it is going to discart the notifification by preventing it to be displayed.
This means that if we handle notifications via firebase/expo the user is always going to receive the notificifation even if he have opted out.
Hi @danny88dam. Can you clarify
addNotificationResponseReceivedListener doesn't trigger when the user taps on the notification, instead when the user tap on the notification handled vai firebase/expo method onMessageReceived addNotificationResponseReceivedListener seems to trrigger regularly
I'm listening for pushes through the expo/firebase method and wasn't aware of any other way.
useEffect(() => {
const subscription = Notifications.addNotificationResponseReceivedListener(response => {
const payload = response.notification.request.content.data;
handlePush(payload);
});
return () => {
subscription.remove();
};
}, [handlePush]);
As for opting out on Android, I didn't know about this either. How can a user opt out of pushes in a way that the push is still sent?
Hi @spenceps I used the same technique, in my case the first thing the listener addNotificationResponseReceivedListener
does is console.log Platform.OS
and push data.
When the user taps on push nofication handled by modified ExpoMarketingCloudSdkApplicationLifecycleListener
, the interaction doesn't trigger the listener because I don't read anything in console.
Regarding the issue of still receiving push notifications even though the user has opted out, let me better explain the scenario.
The SFMC api provides methods enablePush
and disablePush
to opt-in and opt-put.
These methods do not work at OS permission level, but only at the application level and the simply tell MC some sort of flat representing whether the user wants to receive notification.
In my test seems that this "flag" is consider only in app SF MC sdk, maybe encapsulated somewhere here
open class ExpoFirebaseMessagingService : ExpoFirebaseMessagingService() {
override fun onMessageReceived(remoteMessage: RemoteMessage) {
if (PushMessageManager.isMarketingCloudPush(remoteMessage)) {
SFMCSdk.requestSdk { sdk ->
sdk.mp {
it.pushMessageManager.handleMessage(remoteMessage) // MAYBE ENCAPSULATED HERE
}
}
super.onMessageReceived(remoteMessage); // I ADDED THIS LINE FOR TEST
} else {
super.onMessageReceived(remoteMessage)
}
}
}
So only if the notification is handled by pushMessageManager.handleMessage
the opt-in/out flag is considered, notification eventually discarted and not notify to the os.
At least this is what I seem to have understood so far
I'm wondering if we should be using the NotificationManager.getDefaultNotificationBuilder
in order to maintain existing sfmc handling behavior as per Full Control Customization example.
I threw some code together based on that example and @spenceps work. Haven't tested it though.
setNotificationCustomizationOptions(
NotificationCustomizationOptions.create { context, notificationMessage ->
val builder = NotificationManager.getDefaultNotificationBuilder(
context,
notificationMessage,
NotificationManager.createDefaultNotificationChannel(context),
R.drawable.notification_icon
)
builder.setContentIntent(
NotificationManager.redirectIntentForAnalytics(
context,
PendingIntent.getActivity(
context,
Random.Default.nextInt(),
context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply {
// Add custom keys to the intent
notificationMessage.customKeys.forEach { (key, value) ->
putExtra(key, value)
}
if (!notificationMessage.url.isNullOrEmpty()) {
putExtra("url", notificationMessage.url)
}
},
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
),
notificationMessage,
true
)
)
}
)
Hi @andrejpavlovic I tested your patch and it seems to work well. Thank you, @spenceps thank you too
@andrejpavlovic - I tried this out as well and it worked. Thanks everyone.
@andrejpavlovic - Would this change mess up anyone else that is using this? I'm wondering if this change should be added to the next release.
Hi, I integrated this package in my project and everything works well on iOS devices, but on Android I'm encountering some problems
The os correctly receive the push notifications and shows previews with title/text and image (if set), but when the user taps on it and opens the app the notification request seems to be empty, here and example printed via console.log
{ "notification": { "request": { "trigger": { "channelId": null, "type": "push" }, "content": { "title": null, "dataString": null, "body": null }, "identifier": null }, "date": 0 }, "actionIdentifier": "expo.modules.notifications.actions.DEFAULT" }
For debugging purpose I also send push notification via Expo Push Notification Tool https://expo.dev/notifications and it works well either on iOS and Android devices, so the issue seems to be related to MC package, here an example of received notification from expo tool
{ "notification": { "request": { "trigger": { "remoteMessage": { "originalPriority": 2, "sentTime": 1722597307339, "notification": { "usesDefaultVibrateSettings": false, "color": null, "channelId": null, "visibility": null, "sound": null, "tag": null, "bodyLocalizationArgs": null, "imageUrl": null, "title": "title", "vibrateTimings": null, "ticker": null, "eventTime": null, "body": "text android", "titleLocalizationKey": null, "notificationPriority": null, "icon": null, "usesDefaultLightSettings": false, "sticky": false, "link": null, "titleLocalizationArgs": null, "bodyLocalizationKey": null, "usesDefaultSound": false, "clickAction": null, "localOnly": false, "lightSettings": null, "notificationCount": null }, "data": { "message": "text android", "title": "title", "body": "{}", "scopeKey": "****************", "experienceId": "****************", "projectId": "****************" }, "to": null, "ttl": 0, "collapseKey": "****************", "messageType": null, "priority": 2, "from": "****************", "messageId": "****************" }, "channelId": null, "type": "push" }, "content": { "title": "title", "badge": null, "autoDismiss": true, "data": {}, "body": "text android", "sound": "default", "sticky": false, "subtitle": null }, "identifier": "****************" }, "date": 1722597307339 }, "actionIdentifier": "expo.modules.notifications.actions.DEFAULT" }
the the push notification is logged from the notification response received listener
Notifications.addNotificationResponseReceivedListener(async (response) => { console.log(JSON.stringify(response)) ...
Does anyone have any ideas?