diff --git a/.editorconfig b/.editorconfig
index 3b8b35f5..b2d7e6fa 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -18,7 +18,7 @@ tab_width = 4
end_of_line = crlf
trim_trailing_whitespace = true
insert_final_newline = false
-max_line_length = 140
+max_line_length = 200
csharp_indent_block_contents = true
csharp_indent_braces = false
diff --git a/Docs/FAQ.md b/Docs/FAQ.md
index 53152c84..6f216032 100644
--- a/Docs/FAQ.md
+++ b/Docs/FAQ.md
@@ -29,10 +29,11 @@ If you receive an HTTP 429 you may need to back-off and send your messages later
### Why are my notifications not being delivered?
Common reasons for notification delivery issues include:
- Incorrect Firebase configuration: Wrong package name or bundle identifier. Wrong google-services.json or GoogleService-Info.plist file used.
-- The app being killed or not running in the background.
-- Notification permissions not configured in AndroidManifest and/or user not asked for giving consent.
-- Notification events not subscribed.
-- Network connectivity issues.
+- Notification permissions not configured in AndroidManifest.xml.
+- Notification permissions not requested from user.
+- Notification events such as NotificationReceived not subscribed.
+- Network connectivity issues on the receiving device. Particularly, if you use a simulator/emulator on a computer which is connected to slow/fragile network connectivity.
+- Wait some seconds. Delivery of push notifications is not always immediate.
### How can I debug notification issues?
- Verify your Firebase configuration and ensure that the correct services files are being used.
diff --git a/Docs/FCM Plugin.FirebasePushNotifications.postman_collection.json b/Docs/FCM Plugin.FirebasePushNotifications.postman_collection.json
index ac2a9e83..12b93545 100644
--- a/Docs/FCM Plugin.FirebasePushNotifications.postman_collection.json
+++ b/Docs/FCM Plugin.FirebasePushNotifications.postman_collection.json
@@ -36,7 +36,113 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"message\": {\r\n \"token\": \"{{fcm_token}}\",\r\n \"notification\": {\r\n \"title\": \"Notification title\",\r\n \"body\": \"Notification body sent @ {{currentDateTime}}\"\r\n }\r\n }\r\n}"
+ "raw": "{\r\n \"message\": {\r\n \"token\": \"{{fcm_token}}\",\r\n \"notification\": {\r\n \"title\": \"Notification Title\",\r\n \"body\": \"Notification body @ {{currentDateTime}}\"\r\n }\r\n }\r\n}"
+ },
+ "url": {
+ "raw": "https://fcm.googleapis.com/v1/projects/{{project_id}}/messages:send",
+ "protocol": "https",
+ "host": [
+ "fcm",
+ "googleapis",
+ "com"
+ ],
+ "path": [
+ "v1",
+ "projects",
+ "{{project_id}}",
+ "messages:send"
+ ],
+ "query": [
+ {
+ "key": "",
+ "value": null,
+ "disabled": true
+ }
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Notification Message with priority=low",
+ "event": [
+ {
+ "listen": "prerequest",
+ "script": {
+ "exec": [
+ "var moment = require('moment');",
+ "pm.environment.set('currentDateTime', moment().format((\"YYYY-MM-DD HH:mm:ss\")));",
+ ""
+ ],
+ "type": "text/javascript",
+ "packages": {}
+ }
+ }
+ ],
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\r\n \"message\": {\r\n \"token\": \"{{fcm_token}}\",\r\n \"notification\": {\r\n \"title\": \"Notification Title\",\r\n \"body\": \"Low priority notification message @ {{currentDateTime}}\"\r\n },\r\n \"data\": {\r\n \"priority\": \"low\"\r\n }\r\n }\r\n}"
+ },
+ "url": {
+ "raw": "https://fcm.googleapis.com/v1/projects/{{project_id}}/messages:send",
+ "protocol": "https",
+ "host": [
+ "fcm",
+ "googleapis",
+ "com"
+ ],
+ "path": [
+ "v1",
+ "projects",
+ "{{project_id}}",
+ "messages:send"
+ ],
+ "query": [
+ {
+ "key": "",
+ "value": null,
+ "disabled": true
+ }
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Notification Message with priority=default",
+ "event": [
+ {
+ "listen": "prerequest",
+ "script": {
+ "exec": [
+ "var moment = require('moment');",
+ "pm.environment.set('currentDateTime', moment().format((\"YYYY-MM-DD HH:mm:ss\")));",
+ ""
+ ],
+ "type": "text/javascript",
+ "packages": {}
+ }
+ }
+ ],
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\r\n \"message\": {\r\n \"token\": \"{{fcm_token}}\",\r\n \"notification\": {\r\n \"title\": \"Notification Title\",\r\n \"body\": \"Default priority notification message @ {{currentDateTime}}\"\r\n },\r\n \"data\": {\r\n \"priority\": \"default\"\r\n }\r\n }\r\n}"
},
"url": {
"raw": "https://fcm.googleapis.com/v1/projects/{{project_id}}/messages:send",
@@ -65,6 +171,73 @@
},
{
"name": "Notification Message with priority=high",
+ "event": [
+ {
+ "listen": "prerequest",
+ "script": {
+ "exec": [
+ "var moment = require('moment');",
+ "pm.environment.set('currentDateTime', moment().format((\"YYYY-MM-DD HH:mm:ss\")));",
+ ""
+ ],
+ "type": "text/javascript",
+ "packages": {}
+ }
+ }
+ ],
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\r\n \"message\": {\r\n \"token\": \"{{fcm_token}}\",\r\n \"notification\": {\r\n \"title\": \"Notification Title\",\r\n \"body\": \"High priority notification message @ {{currentDateTime}}\"\r\n },\r\n \"data\": {\r\n \"priority\": \"high\"\r\n }\r\n }\r\n}"
+ },
+ "url": {
+ "raw": "https://fcm.googleapis.com/v1/projects/{{project_id}}/messages:send",
+ "protocol": "https",
+ "host": [
+ "fcm",
+ "googleapis",
+ "com"
+ ],
+ "path": [
+ "v1",
+ "projects",
+ "{{project_id}}",
+ "messages:send"
+ ],
+ "query": [
+ {
+ "key": "",
+ "value": null,
+ "disabled": true
+ }
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Notification Message with priority=high (2)",
+ "event": [
+ {
+ "listen": "prerequest",
+ "script": {
+ "exec": [
+ "var moment = require('moment');",
+ "pm.environment.set('currentDateTime', moment().format((\"YYYY-MM-DD HH:mm:ss\")));",
+ ""
+ ],
+ "type": "text/javascript",
+ "packages": {}
+ }
+ }
+ ],
"request": {
"method": "POST",
"header": [
@@ -75,7 +248,7 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"message\": {\r\n \"token\": \"{{fcm_token}}\",\r\n \"notification\": {\r\n \"title\": \"Notification title\",\r\n \"body\": \"Notification body\"\r\n },\r\n \"data\": {\r\n \"priority\": \"high\"\r\n }\r\n }\r\n}"
+ "raw": "{\r\n \"message\": {\r\n \"notification\": {\r\n \"title\": \"Notification Title\",\r\n \"body\": \"High priority notification message @ {{currentDateTime}}\"\r\n },\r\n \"token\": \"{{fcm_token}}\",\r\n \"data\": {\r\n \"eventId\": \"32935962\",\r\n \"product_id\": \"111881503\",\r\n \"location\": \"{\\\"Timestamp\\\":1749168728,\\\"DateTime\\\":\\\"2025-06-06T00:12:08Z\\\",\\\"Longitude\\\":0,\\\"Latitude\\\":0,\\\"Altitude\\\":0,\\\"Accuracy\\\":17.0,\\\"Course\\\":null,\\\"Speed\\\":null}\",\r\n \"priority\": \"high\"\r\n },\r\n \"android\": {\r\n \"priority\": \"HIGH\",\r\n \"ttl\": \"2419200s\"\r\n }\r\n }\r\n}"
},
"url": {
"raw": "https://fcm.googleapis.com/v1/projects/{{project_id}}/messages:send",
@@ -153,7 +326,7 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"message\": {\r\n \"token\": \"{{fcm_token}}\",\r\n \"notification\": {\r\n \"title\": \"Notification title\",\r\n \"body\": \"Notification body\"\r\n },\r\n \"data\": {\r\n \"priority\": \"high\",\r\n \"large_icon\": \"ic_notification_icon_large\"\r\n }\r\n }\r\n}"
+ "raw": "{\r\n \"message\": {\r\n \"token\": \"{{fcm_token}}\",\r\n \"notification\": {\r\n \"title\": \"Notification title\",\r\n \"body\": \"Notification body\"\r\n },\r\n \"data\": {\r\n \"large_icon\": \"ic_notification_icon_large\"\r\n }\r\n }\r\n}"
},
"url": {
"raw": "https://fcm.googleapis.com/v1/projects/{{project_id}}/messages:send",
@@ -192,7 +365,46 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"message\": {\r\n \"token\": \"{{fcm_token}}\",\r\n \"notification\": {\r\n \"title\": \"Notification title\",\r\n \"body\": \"Notification body\"\r\n },\r\n \"data\": {\r\n \"priority\": \"high\",\r\n \"large_icon\": \"https://firebase.google.com/static/images/brand-guidelines/logo-vertical.png\",\r\n \"bigtextstyle\": \"0\"\r\n }\r\n }\r\n}"
+ "raw": "{\r\n \"message\": {\r\n \"token\": \"{{fcm_token}}\",\r\n \"notification\": {\r\n \"title\": \"Notification title\",\r\n \"body\": \"Notification body\"\r\n },\r\n \"data\": {\r\n \"large_icon\": \"https://firebase.google.com/static/images/brand-guidelines/logo-vertical.png\",\r\n \"bigtextstyle\": \"0\"\r\n }\r\n }\r\n}"
+ },
+ "url": {
+ "raw": "https://fcm.googleapis.com/v1/projects/{{project_id}}/messages:send",
+ "protocol": "https",
+ "host": [
+ "fcm",
+ "googleapis",
+ "com"
+ ],
+ "path": [
+ "v1",
+ "projects",
+ "{{project_id}}",
+ "messages:send"
+ ],
+ "query": [
+ {
+ "key": "",
+ "value": null,
+ "disabled": true
+ }
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Notification Message with data payload",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\r\n \"message\": {\r\n \"token\": \"{{fcm_token}}\",\r\n \"notification\": {\r\n \"title\": \"Notification title\",\r\n \"body\": \"Notification body\"\r\n },\r\n \"data\": {\r\n \"key1\": \"value1\",\r\n \"key2\": \"value2\"\r\n }\r\n }\r\n}"
},
"url": {
"raw": "https://fcm.googleapis.com/v1/projects/{{project_id}}/messages:send",
@@ -216,11 +428,438 @@
}
]
}
- },
- "response": []
- },
- {
- "name": "Notification Message with priority=low",
+ },
+ "response": []
+ },
+ {
+ "name": "Notification Message with data payload",
+ "event": [
+ {
+ "listen": "prerequest",
+ "script": {
+ "exec": [
+ ""
+ ],
+ "type": "text/javascript",
+ "packages": {}
+ }
+ }
+ ],
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\r\n \"message\": {\r\n \"token\": \"{{fcm_token}}\",\r\n \"notification\": {\r\n \"title\": \"Medication Notification\",\r\n \"body\": \"Test notification sent via Postman\"\r\n },\r\n \"data\": {\r\n \"notificationAction\": \"medicationReminder\",\r\n \"reminderId\": \"85799965-48fc-11ef-90b6-6740da731683\",\r\n \"notifyDate\": \"2024-07-25T04:00Z\",\r\n \"medications[0].medicationRequestId\": \"f24d43aa-3290-41f0-95dc-4f5525532a03\",\r\n \"medications[0].medicationName\": \"Medication 1\",\r\n \"medications[0].dosage\": \"1\",\r\n \"userNotificationMessageId\": \"-490366181\"\r\n }\r\n }\r\n}"
+ },
+ "url": {
+ "raw": "https://fcm.googleapis.com/v1/projects/{{project_id}}/messages:send",
+ "protocol": "https",
+ "host": [
+ "fcm",
+ "googleapis",
+ "com"
+ ],
+ "path": [
+ "v1",
+ "projects",
+ "{{project_id}}",
+ "messages:send"
+ ],
+ "query": [
+ {
+ "key": "",
+ "value": null,
+ "disabled": true
+ }
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Notification Message with localizable keys",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\r\n \"message\": {\r\n \"token\": \"{{fcm_token}}\",\r\n \"notification\": {\r\n \"title\": \"Notification title\",\r\n \"body\": \"Notification body\"\r\n },\r\n \"data\": {\r\n \"key1\": \"value1\",\r\n \"key2\": \"value2\"\r\n }\r\n }\r\n}"
+ },
+ "url": {
+ "raw": "https://fcm.googleapis.com/v1/projects/{{project_id}}/messages:send",
+ "protocol": "https",
+ "host": [
+ "fcm",
+ "googleapis",
+ "com"
+ ],
+ "path": [
+ "v1",
+ "projects",
+ "{{project_id}}",
+ "messages:send"
+ ],
+ "query": [
+ {
+ "key": "",
+ "value": null,
+ "disabled": true
+ }
+ ]
+ }
+ },
+ "response": []
+ }
+ ]
+ },
+ {
+ "name": "Data Messages",
+ "item": [
+ {
+ "name": "Data Message",
+ "event": [
+ {
+ "listen": "prerequest",
+ "script": {
+ "exec": [
+ "var moment = require('moment');",
+ "pm.environment.set('currentDateTime', moment().format((\"YYYY-MM-DD HH:mm:ss\")));",
+ ""
+ ],
+ "type": "text/javascript",
+ "packages": {}
+ }
+ }
+ ],
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\r\n \"message\": {\r\n \"token\": \"{{fcm_token}}\",\r\n \"data\": {\r\n \"key1\": \"value1\",\r\n \"key2\": \"{{currentDateTime}}\"\r\n }\r\n }\r\n}"
+ },
+ "url": {
+ "raw": "https://fcm.googleapis.com/v1/projects/{{project_id}}/messages:send",
+ "protocol": "https",
+ "host": [
+ "fcm",
+ "googleapis",
+ "com"
+ ],
+ "path": [
+ "v1",
+ "projects",
+ "{{project_id}}",
+ "messages:send"
+ ],
+ "query": [
+ {
+ "key": "",
+ "value": null,
+ "disabled": true
+ }
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Data Message with priority=high",
+ "event": [
+ {
+ "listen": "prerequest",
+ "script": {
+ "exec": [
+ "var moment = require('moment');",
+ "pm.environment.set('currentDateTime', moment().format((\"YYYY-MM-DD HH:mm:ss\")));",
+ ""
+ ],
+ "type": "text/javascript",
+ "packages": {}
+ }
+ }
+ ],
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\r\n \"message\": {\r\n \"token\": \"{{fcm_token}}\",\r\n \"data\": {\r\n \"key1\": \"value1\",\r\n \"key2\": \"{{currentDateTime}}\",\r\n \"priority\": \"high\"\r\n }\r\n }\r\n}"
+ },
+ "url": {
+ "raw": "https://fcm.googleapis.com/v1/projects/{{project_id}}/messages:send",
+ "protocol": "https",
+ "host": [
+ "fcm",
+ "googleapis",
+ "com"
+ ],
+ "path": [
+ "v1",
+ "projects",
+ "{{project_id}}",
+ "messages:send"
+ ],
+ "query": [
+ {
+ "key": "",
+ "value": null,
+ "disabled": true
+ }
+ ]
+ }
+ },
+ "response": []
+ }
+ ]
+ },
+ {
+ "name": "Channels",
+ "item": [
+ {
+ "name": "Notification Message with channel_id=test_channel_1",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\r\n \"message\": {\r\n \"token\": \"{{fcm_token}}\",\r\n \"notification\": {\r\n \"title\": \"Notification test_channel_1\",\r\n \"body\": \"Notification to channel_id=test_channel_1\"\r\n },\r\n \"data\": {\r\n \"channel_id\": \"test_channel_1\"\r\n }\r\n }\r\n}"
+ },
+ "url": {
+ "raw": "https://fcm.googleapis.com/v1/projects/{{project_id}}/messages:send",
+ "protocol": "https",
+ "host": [
+ "fcm",
+ "googleapis",
+ "com"
+ ],
+ "path": [
+ "v1",
+ "projects",
+ "{{project_id}}",
+ "messages:send"
+ ],
+ "query": [
+ {
+ "key": "",
+ "value": null,
+ "disabled": true
+ }
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Notification Message with channel_id=test_channel_2",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\r\n \"message\": {\r\n \"token\": \"{{fcm_token}}\",\r\n \"notification\": {\r\n \"title\": \"Notification test_channel_2\",\r\n \"body\": \"Notification to channel_id=test_channel_2\"\r\n },\r\n \"data\": {\r\n \"channel_id\": \"test_channel_2\"\r\n }\r\n }\r\n}"
+ },
+ "url": {
+ "raw": "https://fcm.googleapis.com/v1/projects/{{project_id}}/messages:send",
+ "protocol": "https",
+ "host": [
+ "fcm",
+ "googleapis",
+ "com"
+ ],
+ "path": [
+ "v1",
+ "projects",
+ "{{project_id}}",
+ "messages:send"
+ ],
+ "query": [
+ {
+ "key": "",
+ "value": null,
+ "disabled": true
+ }
+ ]
+ }
+ },
+ "response": []
+ }
+ ]
+ },
+ {
+ "name": "Topics",
+ "item": [
+ {
+ "name": "Notification Message with topic=general",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "exec": [
+ ""
+ ],
+ "type": "text/javascript",
+ "packages": {}
+ }
+ },
+ {
+ "listen": "prerequest",
+ "script": {
+ "exec": [
+ "var moment = require('moment');",
+ "pm.environment.set('currentDateTime', moment().format((\"YYYY-MM-DD HH:mm:ss\")));",
+ ""
+ ],
+ "type": "text/javascript",
+ "packages": {}
+ }
+ }
+ ],
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\r\n \"message\": {\r\n \"topic\": \"general\",\r\n \"notification\": {\r\n \"title\": \"Notification Title\",\r\n \"body\": \"Topic body @ {{currentDateTime}}\"\r\n }\r\n }\r\n}"
+ },
+ "url": {
+ "raw": "https://fcm.googleapis.com/v1/projects/{{project_id}}/messages:send",
+ "protocol": "https",
+ "host": [
+ "fcm",
+ "googleapis",
+ "com"
+ ],
+ "path": [
+ "v1",
+ "projects",
+ "{{project_id}}",
+ "messages:send"
+ ]
+ },
+ "description": "## Send Message to Topic\n\nThis endpoint allows you to send a message to a specified topic in Firebase Cloud Messaging (FCM). It is useful for broadcasting notifications to multiple subscribers who are subscribed to the given topic.\n\n### Request\n\nThe request is a POST request to the following URL:\n\n```\nPOST https://fcm.googleapis.com/v1/projects/{{project_id}}/messages:send\n\n ```\n\n#### Request Body\n\nThe request body must be in JSON format and contains the following parameters:\n\n- `message`: An object that defines the message to be sent.\n \n - `topic`: A string that specifies the topic to which the message is sent.\n \n - `notification`: An object that contains the notification details.\n \n - `title`: A string representing the title of the notification.\n \n - `body`: A string that contains the body text of the notification.\n \n\n**Example Request Body:**\n\n``` json\n{\n \"message\": {\n \"topic\": \"general\",\n \"notification\": {\n \"title\": \"Topic Title\",\n \"body\": \"Topic Body @ {{currentDateTime}}\"\n }\n }\n}\n\n ```\n\n### Response\n\nUpon successful execution, the API will return a response with the following characteristics:\n\n- **Status Code**: `200 OK`\n \n- **Content-Type**: `application/json`\n \n- **Response Body**: An object that contains a `name` field, which indicates the identifier of the sent message.\n \n\n**Example Response:**\n\n``` json\n{\n \"name\": \"\"\n}\n\n ```\n\n### Usage Notes\n\n- Ensure that the `topic` specified in the request matches an existing topic that subscribers are registered to.\n \n- The `title` and `body` fields in the notification are customizable to provide relevant information to the users receiving the notifications.\n \n- This endpoint is part of the Firebase Cloud Messaging service, and appropriate authentication and permissions must be set up to use it effectively."
+ },
+ "response": []
+ },
+ {
+ "name": "Notification Message with topic=general, tag=chat_111",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "exec": [
+ ""
+ ],
+ "type": "text/javascript",
+ "packages": {}
+ }
+ },
+ {
+ "listen": "prerequest",
+ "script": {
+ "exec": [
+ "var moment = require('moment');",
+ "pm.environment.set('currentDateTime', moment().format((\"YYYY-MM-DD HH:mm:ss\")));",
+ ""
+ ],
+ "type": "text/javascript",
+ "packages": {}
+ }
+ }
+ ],
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\r\n \"message\": {\r\n \"topic\": \"general\",\r\n \"notification\": {\r\n \"title\": \"Notification Title\",\r\n \"body\": \"Topic body @ {{currentDateTime}}\"\r\n },\r\n \"data\": {\r\n \"tag\": \"chat_111\",\r\n \"id\": \"1\"\r\n },\r\n \"apns\": {\r\n \"headers\": {\r\n \"apns-collapse-id\": \"1\"\r\n }\r\n }\r\n }\r\n}"
+ },
+ "url": {
+ "raw": "https://fcm.googleapis.com/v1/projects/{{project_id}}/messages:send",
+ "protocol": "https",
+ "host": [
+ "fcm",
+ "googleapis",
+ "com"
+ ],
+ "path": [
+ "v1",
+ "projects",
+ "{{project_id}}",
+ "messages:send"
+ ]
+ },
+ "description": "## Send Message to Topic\n\nThis endpoint allows you to send a message to a specified topic in Firebase Cloud Messaging (FCM). It is useful for broadcasting notifications to multiple subscribers who are subscribed to the given topic.\n\n### Request\n\nThe request is a POST request to the following URL:\n\n```\nPOST https://fcm.googleapis.com/v1/projects/{{project_id}}/messages:send\n\n ```\n\n#### Request Body\n\nThe request body must be in JSON format and contains the following parameters:\n\n- `message`: An object that defines the message to be sent.\n \n - `topic`: A string that specifies the topic to which the message is sent.\n \n - `notification`: An object that contains the notification details.\n \n - `title`: A string representing the title of the notification.\n \n - `body`: A string that contains the body text of the notification.\n \n\n**Example Request Body:**\n\n``` json\n{\n \"message\": {\n \"topic\": \"general\",\n \"notification\": {\n \"title\": \"Topic Title\",\n \"body\": \"Topic Body @ {{currentDateTime}}\"\n }\n }\n}\n\n ```\n\n### Response\n\nUpon successful execution, the API will return a response with the following characteristics:\n\n- **Status Code**: `200 OK`\n \n- **Content-Type**: `application/json`\n \n- **Response Body**: An object that contains a `name` field, which indicates the identifier of the sent message.\n \n\n**Example Response:**\n\n``` json\n{\n \"name\": \"\"\n}\n\n ```\n\n### Usage Notes\n\n- Ensure that the `topic` specified in the request matches an existing topic that subscribers are registered to.\n \n- The `title` and `body` fields in the notification are customizable to provide relevant information to the users receiving the notifications.\n \n- This endpoint is part of the Firebase Cloud Messaging service, and appropriate authentication and permissions must be set up to use it effectively."
+ },
+ "response": []
+ },
+ {
+ "name": "Notification Message with topic=general, tag=chat_222",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "exec": [
+ ""
+ ],
+ "type": "text/javascript",
+ "packages": {}
+ }
+ },
+ {
+ "listen": "prerequest",
+ "script": {
+ "exec": [
+ "var moment = require('moment');",
+ "pm.environment.set('currentDateTime', moment().format((\"YYYY-MM-DD HH:mm:ss\")));",
+ ""
+ ],
+ "type": "text/javascript",
+ "packages": {}
+ }
+ }
+ ],
"request": {
"method": "POST",
"header": [
@@ -231,7 +870,7 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"message\": {\r\n \"token\": \"{{fcm_token}}\",\r\n \"notification\": {\r\n \"title\": \"Notification title\",\r\n \"body\": \"Notification body\"\r\n },\r\n \"data\": {\r\n \"priority\": \"low\"\r\n }\r\n }\r\n}"
+ "raw": "{\r\n \"message\": {\r\n \"topic\": \"general\",\r\n \"notification\": {\r\n \"title\": \"Notification Title\",\r\n \"body\": \"Topic body @ {{currentDateTime}}\"\r\n },\r\n \"data\": {\r\n \"tag\": \"chat_222\",\r\n \"id\": \"1\"\r\n },\r\n \"apns\": {\r\n \"headers\": {\r\n \"apns-collapse-id\": \"1\"\r\n }\r\n }\r\n }\r\n}"
},
"url": {
"raw": "https://fcm.googleapis.com/v1/projects/{{project_id}}/messages:send",
@@ -246,20 +885,38 @@
"projects",
"{{project_id}}",
"messages:send"
- ],
- "query": [
- {
- "key": "",
- "value": null,
- "disabled": true
- }
]
- }
+ },
+ "description": "## Send Message to Topic\n\nThis endpoint allows you to send a message to a specified topic in Firebase Cloud Messaging (FCM). It is useful for broadcasting notifications to multiple subscribers who are subscribed to the given topic.\n\n### Request\n\nThe request is a POST request to the following URL:\n\n```\nPOST https://fcm.googleapis.com/v1/projects/{{project_id}}/messages:send\n\n ```\n\n#### Request Body\n\nThe request body must be in JSON format and contains the following parameters:\n\n- `message`: An object that defines the message to be sent.\n \n - `topic`: A string that specifies the topic to which the message is sent.\n \n - `notification`: An object that contains the notification details.\n \n - `title`: A string representing the title of the notification.\n \n - `body`: A string that contains the body text of the notification.\n \n\n**Example Request Body:**\n\n``` json\n{\n \"message\": {\n \"topic\": \"general\",\n \"notification\": {\n \"title\": \"Topic Title\",\n \"body\": \"Topic Body @ {{currentDateTime}}\"\n }\n }\n}\n\n ```\n\n### Response\n\nUpon successful execution, the API will return a response with the following characteristics:\n\n- **Status Code**: `200 OK`\n \n- **Content-Type**: `application/json`\n \n- **Response Body**: An object that contains a `name` field, which indicates the identifier of the sent message.\n \n\n**Example Response:**\n\n``` json\n{\n \"name\": \"\"\n}\n\n ```\n\n### Usage Notes\n\n- Ensure that the `topic` specified in the request matches an existing topic that subscribers are registered to.\n \n- The `title` and `body` fields in the notification are customizable to provide relevant information to the users receiving the notifications.\n \n- This endpoint is part of the Firebase Cloud Messaging service, and appropriate authentication and permissions must be set up to use it effectively."
},
"response": []
},
{
- "name": "Notification Message with data payload",
+ "name": "Notification Message with topic=general, priority=low",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "exec": [
+ ""
+ ],
+ "type": "text/javascript",
+ "packages": {}
+ }
+ },
+ {
+ "listen": "prerequest",
+ "script": {
+ "exec": [
+ "var moment = require('moment');",
+ "pm.environment.set('currentDateTime', moment().format((\"YYYY-MM-DD HH:mm:ss\")));",
+ ""
+ ],
+ "type": "text/javascript",
+ "packages": {}
+ }
+ }
+ ],
"request": {
"method": "POST",
"header": [
@@ -270,7 +927,7 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"message\": {\r\n \"token\": \"{{fcm_token}}\",\r\n \"notification\": {\r\n \"title\": \"Notification title\",\r\n \"body\": \"Notification body\"\r\n },\r\n \"data\": {\r\n \"key1\": \"value1\",\r\n \"key2\": \"value2\"\r\n }\r\n }\r\n}"
+ "raw": "{\r\n \"message\": {\r\n \"topic\": \"general\",\r\n \"notification\": {\r\n \"title\": \"Notification Title\",\r\n \"body\": \"Low priority notification message @ {{currentDateTime}}\"\r\n },\r\n \"data\": {\r\n \"priority\": \"low\"\r\n }\r\n }\r\n}"
},
"url": {
"raw": "https://fcm.googleapis.com/v1/projects/{{project_id}}/messages:send",
@@ -298,12 +955,24 @@
"response": []
},
{
- "name": "Notification Message with data payload",
+ "name": "Notification Message with topic=general, priority=default",
"event": [
+ {
+ "listen": "test",
+ "script": {
+ "exec": [
+ ""
+ ],
+ "type": "text/javascript",
+ "packages": {}
+ }
+ },
{
"listen": "prerequest",
"script": {
"exec": [
+ "var moment = require('moment');",
+ "pm.environment.set('currentDateTime', moment().format((\"YYYY-MM-DD HH:mm:ss\")));",
""
],
"type": "text/javascript",
@@ -321,7 +990,7 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"message\": {\r\n \"token\": \"{{fcm_token}}\",\r\n \"notification\": {\r\n \"title\": \"Medication Notification\",\r\n \"body\": \"Test notification sent via Postman\"\r\n },\r\n \"data\": {\r\n \"notificationAction\": \"medicationReminder\",\r\n \"reminderId\": \"85799965-48fc-11ef-90b6-6740da731683\",\r\n \"notifyDate\": \"2024-07-25T04:00Z\",\r\n \"medications[0].medicationRequestId\": \"f24d43aa-3290-41f0-95dc-4f5525532a03\",\r\n \"medications[0].medicationName\": \"Medication 1\",\r\n \"medications[0].dosage\": \"1\",\r\n \"userNotificationMessageId\": \"-490366181\"\r\n }\r\n }\r\n}"
+ "raw": "{\r\n \"message\": {\r\n \"topic\": \"general\",\r\n \"notification\": {\r\n \"title\": \"Notification Title\",\r\n \"body\": \"Default priority notification message @ {{currentDateTime}}\"\r\n },\r\n \"data\": {\r\n \"priority\": \"default\"\r\n }\r\n }\r\n}"
},
"url": {
"raw": "https://fcm.googleapis.com/v1/projects/{{project_id}}/messages:send",
@@ -349,7 +1018,31 @@
"response": []
},
{
- "name": "Notification Message with localizable keys",
+ "name": "Notification Message with topic=general, priority=high",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "exec": [
+ ""
+ ],
+ "type": "text/javascript",
+ "packages": {}
+ }
+ },
+ {
+ "listen": "prerequest",
+ "script": {
+ "exec": [
+ "var moment = require('moment');",
+ "pm.environment.set('currentDateTime', moment().format((\"YYYY-MM-DD HH:mm:ss\")));",
+ ""
+ ],
+ "type": "text/javascript",
+ "packages": {}
+ }
+ }
+ ],
"request": {
"method": "POST",
"header": [
@@ -360,7 +1053,7 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"message\": {\r\n \"token\": \"{{fcm_token}}\",\r\n \"notification\": {\r\n \"title\": \"Notification title\",\r\n \"body\": \"Notification body\"\r\n },\r\n \"data\": {\r\n \"key1\": \"value1\",\r\n \"key2\": \"value2\"\r\n }\r\n }\r\n}"
+ "raw": "{\r\n \"message\": {\r\n \"topic\": \"general\",\r\n \"notification\": {\r\n \"title\": \"Notification Title\",\r\n \"body\": \"High priority notification message @ {{currentDateTime}}\"\r\n },\r\n \"data\": {\r\n \"priority\": \"high\"\r\n }\r\n }\r\n}"
},
"url": {
"raw": "https://fcm.googleapis.com/v1/projects/{{project_id}}/messages:send",
@@ -386,28 +1079,9 @@
}
},
"response": []
- }
- ]
- },
- {
- "name": "Data Messages",
- "item": [
+ },
{
- "name": "Data Message",
- "event": [
- {
- "listen": "prerequest",
- "script": {
- "exec": [
- "var moment = require('moment');",
- "pm.environment.set('currentDateTime', moment().format((\"YYYY-MM-DD HH:mm:ss\")));",
- ""
- ],
- "type": "text/javascript",
- "packages": {}
- }
- }
- ],
+ "name": "Notification Message with topic=general, icon=ic_number_one",
"request": {
"method": "POST",
"header": [
@@ -418,7 +1092,7 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"message\": {\r\n \"token\": \"{{fcm_token}}\",\r\n \"data\": {\r\n \"key1\": \"value1\",\r\n \"key2\": \"{{currentDateTime}}\"\r\n }\r\n }\r\n}"
+ "raw": "{\r\n \"message\": {\r\n \"topic\": \"general\",\r\n \"notification\": {\r\n \"title\": \"Notification title\",\r\n \"body\": \"Notification body\"\r\n },\r\n \"data\": {\r\n \"priority\": \"high\",\r\n \"icon\": \"ic_number_one\"\r\n }\r\n }\r\n}"
},
"url": {
"raw": "https://fcm.googleapis.com/v1/projects/{{project_id}}/messages:send",
@@ -446,21 +1120,7 @@
"response": []
},
{
- "name": "Data Message with priority=high",
- "event": [
- {
- "listen": "prerequest",
- "script": {
- "exec": [
- "var moment = require('moment');",
- "pm.environment.set('currentDateTime', moment().format((\"YYYY-MM-DD HH:mm:ss\")));",
- ""
- ],
- "type": "text/javascript",
- "packages": {}
- }
- }
- ],
+ "name": "Notification Message with topic=general, large_icon=ic_notification_icon_large",
"request": {
"method": "POST",
"header": [
@@ -471,7 +1131,7 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"message\": {\r\n \"token\": \"{{fcm_token}}\",\r\n \"data\": {\r\n \"key1\": \"value1\",\r\n \"key2\": \"{{currentDateTime}}\",\r\n \"priority\": \"high\"\r\n }\r\n }\r\n}"
+ "raw": "{\r\n \"message\": {\r\n \"topic\": \"general\",\r\n \"notification\": {\r\n \"title\": \"Notification title\",\r\n \"body\": \"Notification body\"\r\n },\r\n \"data\": {\r\n \"large_icon\": \"ic_notification_icon_large\"\r\n }\r\n }\r\n}"
},
"url": {
"raw": "https://fcm.googleapis.com/v1/projects/{{project_id}}/messages:send",
@@ -497,14 +1157,9 @@
}
},
"response": []
- }
- ]
- },
- {
- "name": "Channels",
- "item": [
+ },
{
- "name": "Notification Message with channel_id=test_channel_1",
+ "name": "Notification Message with topic=general, large_icon=url",
"request": {
"method": "POST",
"header": [
@@ -515,7 +1170,7 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"message\": {\r\n \"token\": \"{{fcm_token}}\",\r\n \"notification\": {\r\n \"title\": \"Notification title\",\r\n \"body\": \"Notification body\"\r\n },\r\n \"data\": {\r\n \"channel_id\": \"test_channel_1\"\r\n }\r\n }\r\n}"
+ "raw": "{\r\n \"message\": {\r\n \"topic\": \"general\",\r\n \"notification\": {\r\n \"title\": \"Notification title\",\r\n \"body\": \"Notification body\"\r\n },\r\n \"data\": {\r\n \"large_icon\": \"https://firebase.google.com/static/images/brand-guidelines/logo-vertical.png\",\r\n \"bigtextstyle\": \"0\"\r\n }\r\n }\r\n}"
},
"url": {
"raw": "https://fcm.googleapis.com/v1/projects/{{project_id}}/messages:send",
@@ -541,17 +1196,12 @@
}
},
"response": []
- }
- ]
- },
- {
- "name": "Topics",
- "item": [
+ },
{
- "name": "Notification Message with topic=general",
+ "name": "Notification Message with topic=subscriber-updates",
"event": [
{
- "listen": "test",
+ "listen": "prerequest",
"script": {
"exec": [
""
@@ -561,11 +1211,9 @@
}
},
{
- "listen": "prerequest",
+ "listen": "test",
"script": {
"exec": [
- "var moment = require('moment');",
- "pm.environment.set('currentDateTime', moment().format((\"YYYY-MM-DD HH:mm:ss\")));",
""
],
"type": "text/javascript",
@@ -583,7 +1231,7 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"message\": {\r\n \"topic\": \"general\",\r\n \"notification\": {\r\n \"title\": \"Topic Title\",\r\n \"body\": \"Topic Body @ {{currentDateTime}}\"\r\n }\r\n }\r\n}"
+ "raw": "{\r\n \"message\": {\r\n \"topic\": \"subscriber-updates\",\r\n \"notification\": {\r\n \"title\": \"NewsMagazine.com\",\r\n \"body\": \"This week's edition is now available.\"\r\n },\r\n \"data\": {\r\n \"volume\": \"3.21.15\",\r\n \"contents\": \"http://www.news-magazine.com/world-week/1234\"\r\n },\r\n \"android\": {\r\n \"priority\": \"normal\"\r\n },\r\n \"apns\": {\r\n \"headers\": {\r\n \"apns-priority\": \"5\"\r\n }\r\n }\r\n }\r\n}"
},
"url": {
"raw": "https://fcm.googleapis.com/v1/projects/{{project_id}}/messages:send",
@@ -604,32 +1252,7 @@
"response": []
},
{
- "name": "Data Message with topic=general",
- "event": [
- {
- "listen": "test",
- "script": {
- "exec": [
- ""
- ],
- "type": "text/javascript",
- "packages": {}
- }
- },
- {
- "listen": "prerequest",
- "script": {
- "exec": [
- "var moment = require('moment');",
- "",
- "pm.environment.set('currentDateTime', moment().format((\"YYYY-MM-DD HH:mm:ss\")));",
- ""
- ],
- "type": "text/javascript",
- "packages": {}
- }
- }
- ],
+ "name": "Notification Message with topic=weather_updates",
"request": {
"method": "POST",
"header": [
@@ -640,7 +1263,7 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"message\": {\r\n \"topic\": \"general\",\r\n \"data\": {\r\n \"key1\": \"value1\",\r\n \"key2\": \"{{currentDateTime}}\"\r\n }\r\n }\r\n}"
+ "raw": "{\r\n \"message\": {\r\n \"condition\": \"'weather_updates' in topics\",\r\n \"notification\": {\r\n \"title\": \"Weather Update\",\r\n \"body\": \"Pleasant with clouds and sun\"\r\n },\r\n \"data\": {\r\n \"sunrise\": \"1684926645\",\r\n \"sunset\": \"1684977332\",\r\n \"temp\": \"292.55\",\r\n \"feels_like\": \"292.87\"\r\n }\r\n }\r\n}"
},
"url": {
"raw": "https://fcm.googleapis.com/v1/projects/{{project_id}}/messages:send",
@@ -661,10 +1284,10 @@
"response": []
},
{
- "name": "Notification Message with topic=subscriber-updates",
+ "name": "Data Message with topic=general",
"event": [
{
- "listen": "prerequest",
+ "listen": "test",
"script": {
"exec": [
""
@@ -674,9 +1297,11 @@
}
},
{
- "listen": "test",
+ "listen": "prerequest",
"script": {
"exec": [
+ "var moment = require('moment');",
+ "pm.environment.set('currentDateTime', moment().format((\"YYYY-MM-DD HH:mm:ss\")));",
""
],
"type": "text/javascript",
@@ -694,7 +1319,7 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"message\":{\r\n \"topic\":\"subscriber-updates\",\r\n \"notification\":{\r\n \"title\" : \"NewsMagazine.com\",\r\n \"body\" : \"This week's edition is now available.\"\r\n },\r\n \"data\" : {\r\n \"volume\" : \"3.21.15\",\r\n \"contents\" : \"http://www.news-magazine.com/world-week/1234\"\r\n },\r\n \"android\":{\r\n \"priority\":\"normal\"\r\n },\r\n \"apns\":{\r\n \"headers\":{\r\n \"apns-priority\":\"5\"\r\n }\r\n }\r\n }\r\n}"
+ "raw": "{\r\n \"message\": {\r\n \"topic\": \"general\",\r\n \"data\": {\r\n \"key1\": \"value1\",\r\n \"key2\": \"{{currentDateTime}}\"\r\n }\r\n }\r\n}"
},
"url": {
"raw": "https://fcm.googleapis.com/v1/projects/{{project_id}}/messages:send",
@@ -715,7 +1340,21 @@
"response": []
},
{
- "name": "Notification Message with topic=weather_updates",
+ "name": "Data Message with topic=general, priority=high",
+ "event": [
+ {
+ "listen": "prerequest",
+ "script": {
+ "exec": [
+ "var moment = require('moment');",
+ "pm.environment.set('currentDateTime', moment().format((\"YYYY-MM-DD HH:mm:ss\")));",
+ ""
+ ],
+ "type": "text/javascript",
+ "packages": {}
+ }
+ }
+ ],
"request": {
"method": "POST",
"header": [
@@ -726,7 +1365,7 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"message\": {\r\n \"condition\": \"'weather_updates' in topics\",\r\n \"notification\": {\r\n \"title\": \"Weather Update\",\r\n \"body\": \"Pleasant with clouds and sun\"\r\n },\r\n \"data\": {\r\n \"sunrise\": \"1684926645\",\r\n \"sunset\": \"1684977332\",\r\n \"temp\": \"292.55\",\r\n \"feels_like\": \"292.87\"\r\n }\r\n }\r\n}"
+ "raw": "{\r\n \"message\": {\r\n \"topic\": \"general\",\r\n \"data\": {\r\n \"key1\": \"value1\",\r\n \"key2\": \"{{currentDateTime}}\",\r\n \"priority\": \"high\"\r\n }\r\n }\r\n}"
},
"url": {
"raw": "https://fcm.googleapis.com/v1/projects/{{project_id}}/messages:send",
@@ -741,6 +1380,13 @@
"projects",
"{{project_id}}",
"messages:send"
+ ],
+ "query": [
+ {
+ "key": "",
+ "value": null,
+ "disabled": true
+ }
]
}
},
@@ -1412,7 +2058,7 @@
"response": []
},
{
- "name": "navigate with priority=high",
+ "name": "meeting_invitation with priority=default",
"request": {
"method": "POST",
"header": [
@@ -1423,7 +2069,7 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"message\": {\r\n \"topic\": \"general\",\r\n \"notification\": {\r\n \"title\": \"Navigation\",\r\n \"body\": \"Want start navigation?\"\r\n },\r\n \"data\": {\r\n \"priority\": \"high\",\r\n \"click_action\": \"navigate\"\r\n },\r\n \"apns\": {\r\n \"payload\": {\r\n \"aps\": {\r\n \"category\": \"navigate\"\r\n }\r\n }\r\n }\r\n }\r\n}"
+ "raw": "{\r\n \"message\": {\r\n \"topic\": \"general\",\r\n \"notification\": {\r\n \"title\": \"Meeting Invitation\",\r\n \"body\": \"Want to participate?\"\r\n },\r\n \"data\": {\r\n \"click_action\": \"meeting_invitation\"\r\n },\r\n \"apns\": {\r\n \"payload\": {\r\n \"aps\": {\r\n \"category\": \"meeting_invitation\"\r\n }\r\n }\r\n }\r\n }\r\n}"
},
"url": {
"raw": "https://fcm.googleapis.com/v1/projects/{{project_id}}/messages:send",
@@ -1439,12 +2085,13 @@
"{{project_id}}",
"messages:send"
]
- }
+ },
+ "description": "- meeting_invitation\n \n- priority: default"
},
"response": []
},
{
- "name": "meeting_invitation with priority=default",
+ "name": "message with priority=high",
"request": {
"method": "POST",
"header": [
@@ -1455,7 +2102,7 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"message\": {\r\n \"topic\": \"general\",\r\n \"notification\": {\r\n \"title\": \"Meeting Invitation\",\r\n \"body\": \"Want to participate?\"\r\n },\r\n \"data\": {\r\n \"click_action\": \"meeting_invitation\"\r\n },\r\n \"apns\": {\r\n \"payload\": {\r\n \"aps\": {\r\n \"category\": \"meeting_invitation\"\r\n }\r\n }\r\n }\r\n }\r\n}"
+ "raw": "{\r\n \"message\": {\r\n \"topic\": \"general\",\r\n \"notification\": {\r\n \"title\": \"Message\",\r\n \"body\": \"Hello, how are you?\"\r\n },\r\n \"data\": {\r\n \"click_action\": \"message\",\r\n \"priority\": \"high\"\r\n },\r\n \"apns\": {\r\n \"payload\": {\r\n \"aps\": {\r\n \"category\": \"message\"\r\n }\r\n }\r\n }\r\n }\r\n}"
},
"url": {
"raw": "https://fcm.googleapis.com/v1/projects/{{project_id}}/messages:send",
@@ -1477,7 +2124,7 @@
"response": []
},
{
- "name": "contract with priority=default",
+ "name": "contract with priority=high",
"request": {
"method": "POST",
"header": [
@@ -1488,7 +2135,39 @@
],
"body": {
"mode": "raw",
- "raw": "{\r\n \"message\": {\r\n \"topic\": \"general\",\r\n \"notification\": {\r\n \"title\": \"Contract\",\r\n \"body\": \"Accept contract?\"\r\n },\r\n \"data\": {\r\n \"click_action\": \"contract\"\r\n },\r\n \"apns\": {\r\n \"payload\": {\r\n \"aps\": {\r\n \"category\": \"contract\"\r\n }\r\n }\r\n }\r\n }\r\n}"
+ "raw": "{\r\n \"message\": {\r\n \"topic\": \"general\",\r\n \"notification\": {\r\n \"title\": \"Contract\",\r\n \"body\": \"Accept contract?\"\r\n },\r\n \"data\": {\r\n \"click_action\": \"contract\",\r\n \"priority\": \"high\"\r\n },\r\n \"apns\": {\r\n \"payload\": {\r\n \"aps\": {\r\n \"category\": \"contract\"\r\n }\r\n }\r\n }\r\n }\r\n}"
+ },
+ "url": {
+ "raw": "https://fcm.googleapis.com/v1/projects/{{project_id}}/messages:send",
+ "protocol": "https",
+ "host": [
+ "fcm",
+ "googleapis",
+ "com"
+ ],
+ "path": [
+ "v1",
+ "projects",
+ "{{project_id}}",
+ "messages:send"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "navigate with priority=high",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\r\n \"message\": {\r\n \"topic\": \"general\",\r\n \"notification\": {\r\n \"title\": \"Navigation\",\r\n \"body\": \"Want start navigation?\"\r\n },\r\n \"data\": {\r\n \"priority\": \"high\",\r\n \"click_action\": \"navigate\"\r\n },\r\n \"apns\": {\r\n \"payload\": {\r\n \"aps\": {\r\n \"category\": \"navigate\"\r\n }\r\n }\r\n }\r\n }\r\n}"
},
"url": {
"raw": "https://fcm.googleapis.com/v1/projects/{{project_id}}/messages:send",
diff --git a/Plugin.FirebasePushNotifications.sln b/Plugin.FirebasePushNotifications.sln
index 2df1b09f..4c2cbf82 100644
--- a/Plugin.FirebasePushNotifications.sln
+++ b/Plugin.FirebasePushNotifications.sln
@@ -10,6 +10,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
azure-pipelines.yml = azure-pipelines.yml
LICENSE = LICENSE
README.md = README.md
+ ReleaseNotes.txt = ReleaseNotes.txt
+ global.json = global.json
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Plugin.FirebasePushNotifications", "Plugin.FirebasePushNotifications\Plugin.FirebasePushNotifications.csproj", "{49F1F62D-5A20-49A6-B508-408BE32B0DC6}"
diff --git a/Plugin.FirebasePushNotifications.sln.DotSettings b/Plugin.FirebasePushNotifications.sln.DotSettings
new file mode 100644
index 00000000..d4a07cdf
--- /dev/null
+++ b/Plugin.FirebasePushNotifications.sln.DotSettings
@@ -0,0 +1,2 @@
+
+ True
\ No newline at end of file
diff --git a/Plugin.FirebasePushNotifications/Extensions/DictionaryExtensions.cs b/Plugin.FirebasePushNotifications/Extensions/DictionaryExtensions.cs
index 126afe6f..02a9ada0 100644
--- a/Plugin.FirebasePushNotifications/Extensions/DictionaryExtensions.cs
+++ b/Plugin.FirebasePushNotifications/Extensions/DictionaryExtensions.cs
@@ -17,11 +17,11 @@ public static T GetValueOrDefault(this IDictionary items, string k
return defaultValue;
}
- public static Ty GetValueOrDefault(this IDictionary items, string key, Ty defaultValue = default)
+ public static TY GetValueOrDefault(this IDictionary items, string key, TY defaultValue = default)
{
if (items.TryGetValue(key, out var value))
{
- return (Ty)Convert.ChangeType(value, typeof(Ty));
+ return (TY)Convert.ChangeType(value, typeof(TY));
}
return defaultValue;
diff --git a/Plugin.FirebasePushNotifications/IFirebasePushNotification.cs b/Plugin.FirebasePushNotifications/IFirebasePushNotification.cs
index ebdd4068..fdb757f7 100644
--- a/Plugin.FirebasePushNotifications/IFirebasePushNotification.cs
+++ b/Plugin.FirebasePushNotifications/IFirebasePushNotification.cs
@@ -97,27 +97,27 @@ public interface IFirebasePushNotification
///
/// Subscribe to .
///
- void SubscribeTopic(string topic);
+ Task SubscribeTopicAsync(string topic);
///
/// Subscribe to list of .
///
- void SubscribeTopics(string[] topics);
+ Task SubscribeTopicsAsync(string[] topics);
///
/// Unsubscribe from .
///
- void UnsubscribeTopic(string topic);
+ Task UnsubscribeTopicAsync(string topic);
///
/// Unsubscribe from list of .
///
- void UnsubscribeTopics(string[] topics);
+ Task UnsubscribeTopicsAsync(string[] topics);
///
/// Unsubscribe all topics.
///
- void UnsubscribeAllTopics();
+ Task UnsubscribeAllTopicsAsync();
///
/// Register for push notifications.
diff --git a/Plugin.FirebasePushNotifications/Platforms/Android/Channels/INotificationChannels.cs b/Plugin.FirebasePushNotifications/Platforms/Android/Channels/INotificationChannels.cs
index e6ee353d..5267f542 100644
--- a/Plugin.FirebasePushNotifications/Platforms/Android/Channels/INotificationChannels.cs
+++ b/Plugin.FirebasePushNotifications/Platforms/Android/Channels/INotificationChannels.cs
@@ -1,4 +1,6 @@
-using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.Versioning;
using Android.App;
using Plugin.FirebasePushNotifications.Platforms.Channels;
@@ -103,6 +105,7 @@ public partial interface INotificationChannels
/// Opens the notification channel settings for .
///
/// The notification channel identifier.
+ [SupportedOSPlatform("android26.0")]
void OpenNotificationChannelSettings(string notificationChannelId);
}
}
\ No newline at end of file
diff --git a/Plugin.FirebasePushNotifications/Platforms/Android/Channels/NotificationChannelRequest.cs b/Plugin.FirebasePushNotifications/Platforms/Android/Channels/NotificationChannelRequest.cs
index aa8cb577..35fa4b0c 100644
--- a/Plugin.FirebasePushNotifications/Platforms/Android/Channels/NotificationChannelRequest.cs
+++ b/Plugin.FirebasePushNotifications/Platforms/Android/Channels/NotificationChannelRequest.cs
@@ -13,6 +13,14 @@ public class NotificationChannelRequest
/// Sets or gets, the level of interruption of this notification channel.
/// Default: NotificationImportance.Default
///
+ ///
+ /// Important note:
+ /// The importance of a notification channel cannot be changed programmatically after it is first created.
+ /// Deleting and recreating a notification channel with the same ID does not change its importance.
+ /// Only the user can change the notification settings for an existing channel in system settings.
+ /// If you want to change the importance of a notification channel,
+ /// delete the existing channel and create a new one with a different ID.
+ ///
public NotificationImportance Importance { get; set; } = NotificationImportance.Default;
///
@@ -63,7 +71,7 @@ public class NotificationChannelRequest
public long[] VibrationPattern { get; set; }
///
- /// Sets or gets, whether or not notifications posted to this channel are shown on the lockscreen in full or redacted form.
+ /// Sets or gets, whether notifications posted to this channel are shown on the lockscreen in full or redacted form.
/// Default: NotificationVisibility.Public.
///
public NotificationVisibility LockscreenVisibility { get; set; } = NotificationVisibility.Public;
diff --git a/Plugin.FirebasePushNotifications/Platforms/Android/Channels/NotificationChannels.cs b/Plugin.FirebasePushNotifications/Platforms/Android/Channels/NotificationChannels.cs
index f54d360b..4be326f0 100644
--- a/Plugin.FirebasePushNotifications/Platforms/Android/Channels/NotificationChannels.cs
+++ b/Plugin.FirebasePushNotifications/Platforms/Android/Channels/NotificationChannels.cs
@@ -60,6 +60,11 @@ public IEnumerable ChannelGroups
///
public void SetNotificationChannelGroups([NotNull] NotificationChannelGroupRequest[] notificationChannelGroupRequests)
{
+ if (Build.VERSION.SdkInt < BuildVersionCodes.O)
+ {
+ return;
+ }
+
ArgumentNullException.ThrowIfNull(notificationChannelGroupRequests);
var groupIds = notificationChannelGroupRequests
@@ -68,11 +73,6 @@ public void SetNotificationChannelGroups([NotNull] NotificationChannelGroupReque
this.logger.LogDebug($"SetNotificationChannelGroups: notificationChannelGroupRequests=[{string.Join(",", groupIds)}]");
- if (Build.VERSION.SdkInt < BuildVersionCodes.O)
- {
- return;
- }
-
var notificationChannelGroupIdsToDelete = this.ChannelGroups.Select(c => c.Id);
if (groupIds.Any())
@@ -88,6 +88,11 @@ public void SetNotificationChannelGroups([NotNull] NotificationChannelGroupReque
///
public void CreateNotificationChannelGroups([NotNull] NotificationChannelGroupRequest[] notificationChannelGroupRequests)
{
+ if (Build.VERSION.SdkInt < BuildVersionCodes.O)
+ {
+ return;
+ }
+
ArgumentNullException.ThrowIfNull(notificationChannelGroupRequests);
var groupIds = notificationChannelGroupRequests
@@ -96,11 +101,6 @@ public void CreateNotificationChannelGroups([NotNull] NotificationChannelGroupRe
this.logger.LogDebug($"CreateNotificationChannelGroups: notificationChannelGroupRequests=[{string.Join(",", groupIds)}]");
- if (Build.VERSION.SdkInt < BuildVersionCodes.O)
- {
- return;
- }
-
this.CreateNotificationChannelGroupsInternal(notificationChannelGroupRequests);
}
@@ -135,13 +135,13 @@ public void DeleteNotificationChannelGroup(string groupId)
///
public void DeleteAllNotificationChannelGroups()
{
- this.logger.LogDebug("DeleteAllNotificationChannelGroups");
-
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
{
return;
}
+ this.logger.LogDebug("DeleteAllNotificationChannelGroups");
+
var groupIds = this.ChannelGroups
.Select(g => g.Id)
.ToArray();
@@ -152,15 +152,15 @@ public void DeleteAllNotificationChannelGroups()
///
public void DeleteNotificationChannelGroups([NotNull] string[] groupIds)
{
- ArgumentNullException.ThrowIfNull(groupIds);
-
- this.logger.LogDebug($"DeleteNotificationChannelGroups: groupIds=[{string.Join(",", groupIds)}]");
-
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
{
return;
}
+ ArgumentNullException.ThrowIfNull(groupIds);
+
+ this.logger.LogDebug($"DeleteNotificationChannelGroups: groupIds=[{string.Join(",", groupIds)}]");
+
this.DeleteNotificationChannelGroupsInternal(groupIds);
}
@@ -180,6 +180,11 @@ private void DeleteNotificationChannelGroupsInternal(string[] groupIds)
///
public void SetNotificationChannels([NotNull] NotificationChannelRequest[] notificationChannelRequests)
{
+ if (Build.VERSION.SdkInt < BuildVersionCodes.O)
+ {
+ return;
+ }
+
ArgumentNullException.ThrowIfNull(notificationChannelRequests);
var notificationChannelRequestIds = notificationChannelRequests
@@ -189,36 +194,11 @@ public void SetNotificationChannels([NotNull] NotificationChannelRequest[] notif
this.logger.LogDebug(
$"SetNotificationChannels: notificationChannelRequests=[{string.Join(",", notificationChannelRequestIds)}]");
- if (Build.VERSION.SdkInt < BuildVersionCodes.O)
- {
- return;
- }
-
+ // If no default notification channel is requested, we create it with predefined properties.
if (!notificationChannelRequests.Any(c => c.IsDefault))
{
- var metadata = MetadataHelper.GetMetadata();
- var channelId = metadata.GetString(
- Constants.MetadataDefaultNotificationChannelId,
- Constants.DefaultNotificationChannelId);
-
- var defaultNotificationChannelRequest = new NotificationChannelRequest
- {
- ChannelId = channelId,
- ChannelName = Constants.DefaultNotificationChannelName,
- LockscreenVisibility = NotificationVisibility.Public,
- Importance = this.options.Android.DefaultNotificationImportance,
- IsDefault = true
- };
-
- const string optionsPath = $"options.{nameof(FirebasePushNotificationOptions.Android)}." +
- $"{nameof(FirebasePushNotificationAndroidOptions.NotificationChannels)}";
-
- this.logger.LogDebug(
- $"No default notification channel specified in {optionsPath}. Creating default notification channel with {Environment.NewLine}" +
- $"> ChannelId={defaultNotificationChannelRequest.ChannelId}, {Environment.NewLine}" +
- $"> ChannelName={defaultNotificationChannelRequest.ChannelName}, {Environment.NewLine}" +
- $"> LockscreenVisibility={defaultNotificationChannelRequest.LockscreenVisibility}, {Environment.NewLine}" +
- $"> Importance={defaultNotificationChannelRequest.Importance}");
+ var defaultNotificationChannelId = GetDefaultNotificationChannelIds().First();
+ var defaultNotificationChannelRequest = this.CreateDefaultNotificationChannelRequest(defaultNotificationChannelId);
notificationChannelRequests = notificationChannelRequests
.Prepend(defaultNotificationChannelRequest)
@@ -242,9 +222,114 @@ public void SetNotificationChannels([NotNull] NotificationChannelRequest[] notif
this.CreateNotificationChannelsInternal(notificationChannelRequests);
}
+ public void EnsureDefaultNotificationChannel()
+ {
+ if (Build.VERSION.SdkInt < BuildVersionCodes.O)
+ {
+ return;
+ }
+
+ var existingNotificationChannelIds = this.Channels
+ .Select(c => c.Id)
+ .ToArray();
+
+ // There is no concept of default notification channels in Android.
+ // Therefore, we need to find or create the default notification channel.
+ // 1. If there is no existing notification channel, we create a new one with default properties.
+ // 2. If we have exactly one notification channel, we treat it as the default notification channel.
+ // 3. If we have multiple notification channels,
+ // 3a. try to get the default notification channel from the list of existing notification channels, or
+ // 3b. we treat the first in the list as default notification channel.
+ var defaultNotificationChannelIds = GetDefaultNotificationChannelIds().ToArray();
+ var defaultNotificationChannelId = existingNotificationChannelIds.Length switch
+ {
+ 0 => defaultNotificationChannelIds.First(),
+ 1 => existingNotificationChannelIds[0],
+ _ => existingNotificationChannelIds.FirstOrDefault(c => defaultNotificationChannelIds.Contains(c, StringComparer.InvariantCultureIgnoreCase)) ?? existingNotificationChannelIds[0],
+ };
+
+ var defaultNotificationChannelExists = existingNotificationChannelIds
+ .Any(c => string.Equals(c, defaultNotificationChannelId, StringComparison.InvariantCultureIgnoreCase));
+
+ this.logger.LogDebug(
+ $"EnsureDefaultNotificationChannel: existingNotificationChannelIds=[{string.Join(",", existingNotificationChannelIds)}] " +
+ $"--> defaultNotificationChannelId={defaultNotificationChannelId} ({(defaultNotificationChannelExists ? "existing" : "new")})");
+
+ if (!defaultNotificationChannelExists)
+ {
+ // If no default notification channel exists, we create one with predefined properties.
+ var defaultNotificationChannelRequest = this.CreateDefaultNotificationChannelRequest(defaultNotificationChannelId);
+ this.CreateNotificationChannelsInternal(new[] { defaultNotificationChannelRequest });
+ }
+ else
+ {
+ this.Channels.SetDefaultNotificationChannelIdInternal(defaultNotificationChannelId);
+ }
+ }
+
+ private NotificationChannelRequest CreateDefaultNotificationChannelRequest(string defaultNotificationChannelId)
+ {
+ var defaultNotificationChannelRequest = new NotificationChannelRequest
+ {
+ ChannelId = defaultNotificationChannelId,
+ ChannelName = Constants.DefaultNotificationChannelName,
+ IsDefault = true,
+ LockscreenVisibility = NotificationVisibility.Public,
+ Importance = this.options.Android.DefaultNotificationImportance,
+ };
+
+ const string optionsPath = $"options.{nameof(FirebasePushNotificationOptions.Android)}." +
+ $"{nameof(FirebasePushNotificationAndroidOptions.NotificationChannels)}";
+
+ this.logger.LogWarning(
+ $"Missing default notification channel (IsDefault=true) in {optionsPath}.{Environment.NewLine}" +
+ $"A default notification channel with the following properties will be created: {Environment.NewLine}" +
+ $"> ChannelId={defaultNotificationChannelRequest.ChannelId}, {Environment.NewLine}" +
+ $"> ChannelName={defaultNotificationChannelRequest.ChannelName}, {Environment.NewLine}" +
+ $"> IsDefault=true, {Environment.NewLine}" +
+ $"> LockscreenVisibility=NotificationVisibility.Public, {Environment.NewLine}" +
+ $"> Importance=NotificationImportance.{defaultNotificationChannelRequest.Importance}");
+
+ return defaultNotificationChannelRequest;
+ }
+
+ private static IEnumerable GetDefaultNotificationChannelIds()
+ {
+ // Try to get the default notification channel ID from AndroidManifest.xml
+ {
+ var metadata = MetadataHelper.GetMetadata();
+ var defaultNotificationChannelId = metadata.GetString(
+ key: Constants.MetadataDefaultNotificationChannelId,
+ defaultValue: null);
+
+ if (!string.IsNullOrEmpty(defaultNotificationChannelId))
+ {
+ yield return defaultNotificationChannelId;
+ }
+ }
+
+ // Try to get the default notification channel ID from static string defined in this library
+ {
+ var defaultNotificationChannelId = Constants.DefaultNotificationChannelId;
+ if (!string.IsNullOrEmpty(defaultNotificationChannelId))
+ {
+ yield return defaultNotificationChannelId;
+ }
+ else
+ {
+ yield return "default_channel_id";
+ }
+ }
+ }
+
///
public void CreateNotificationChannels([NotNull] NotificationChannelRequest[] notificationChannelRequests)
{
+ if (Build.VERSION.SdkInt < BuildVersionCodes.O)
+ {
+ return;
+ }
+
ArgumentNullException.ThrowIfNull(notificationChannelRequests);
var newChannelIds = notificationChannelRequests
@@ -253,11 +338,6 @@ public void CreateNotificationChannels([NotNull] NotificationChannelRequest[] no
this.logger.LogDebug($"CreateNotificationChannels: notificationChannelRequests=[{string.Join(",", newChannelIds)}]");
- if (Build.VERSION.SdkInt < BuildVersionCodes.O)
- {
- return;
- }
-
if (newChannelIds.Length == 0)
{
return;
@@ -323,6 +403,7 @@ private void CreateNotificationChannelsInternal([NotNull] NotificationChannelReq
}
else
{
+ this.logger.LogDebug($"Creating notification channel '{notificationChannelRequest.ChannelId}'");
this.notificationManager.CreateNotificationChannel(notificationChannel);
}
}
@@ -337,13 +418,13 @@ private void CreateNotificationChannelsInternal([NotNull] NotificationChannelReq
///
public void DeleteAllNotificationChannels()
{
- this.logger.LogDebug("DeleteAllNotificationChannels");
-
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
{
return;
}
+ this.logger.LogDebug("DeleteAllNotificationChannels");
+
var defaultNotificationChannelId = this.Channels.DefaultNotificationChannelId;
var allNotificationChannelIds = this.Channels
@@ -365,15 +446,15 @@ public void DeleteNotificationChannel([NotNull] string notificationChannelId)
///
public void DeleteNotificationChannels([NotNull] string[] notificationChannelIds)
{
- ArgumentNullException.ThrowIfNull(notificationChannelIds);
-
- this.logger.LogDebug($"DeleteNotificationChannels: notificationChannelIds=[{string.Join(",", notificationChannelIds)}]");
-
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
{
return;
}
+ ArgumentNullException.ThrowIfNull(notificationChannelIds);
+
+ this.logger.LogDebug($"DeleteNotificationChannels: notificationChannelIds=[{string.Join(",", notificationChannelIds)}]");
+
var channelIds = this.Channels
.Select(c => c.Id)
.ToArray();
@@ -421,6 +502,7 @@ private void CreateDefaultNotificationChannelInternal()
// TODO
}
+ ///
public void OpenNotificationSettings()
{
this.logger.LogDebug("OpenNotificationSettings");
@@ -428,10 +510,27 @@ public void OpenNotificationSettings()
try
{
var context = Android.App.Application.Context;
- var newIntent = new Intent(Settings.ActionAppNotificationSettings);
- newIntent.SetFlags(ActivityFlags.NewTask);
- newIntent.PutExtra(Settings.ExtraAppPackage, context.PackageName);
- context.StartActivity(newIntent);
+
+ var intent = new Intent();
+ var sdkInt = (int)Build.VERSION.SdkInt;
+ if (sdkInt >= (int)BuildVersionCodes.O) // >= Android 8.0
+ {
+ intent.SetAction(Settings.ActionAppNotificationSettings);
+ intent.PutExtra(Settings.ExtraAppPackage, context.PackageName);
+ intent.PutExtra(Settings.ExtraChannelId, context.ApplicationInfo!.Uid);
+ }
+ else if (sdkInt is >= (int)BuildVersionCodes.Lollipop and < (int)BuildVersionCodes.O) // >= Android 5.0 && < Android 8.0
+ {
+ intent.SetAction(Settings.ActionAppNotificationSettings);
+ intent.PutExtra("app_package", context.PackageName);
+ intent.PutExtra("app_uid", context.ApplicationInfo!.Uid);
+ }
+ else
+ {
+ return;
+ }
+
+ context.StartActivity(intent);
}
catch (Exception e)
{
@@ -439,6 +538,7 @@ public void OpenNotificationSettings()
}
}
+ ///
public void OpenNotificationChannelSettings([NotNull] string notificationChannelId)
{
this.logger.LogDebug($"OpenNotificationChannelSettings: notificationChannelId={notificationChannelId}");
diff --git a/Plugin.FirebasePushNotifications/Platforms/Android/FirebasePushNotificationManager.cs b/Plugin.FirebasePushNotifications/Platforms/Android/FirebasePushNotificationManager.cs
index 65e657e5..c8f2c972 100644
--- a/Plugin.FirebasePushNotifications/Platforms/Android/FirebasePushNotificationManager.cs
+++ b/Plugin.FirebasePushNotifications/Platforms/Android/FirebasePushNotificationManager.cs
@@ -43,8 +43,23 @@ private void ConfigurePlatform()
{
this.logger.LogDebug("ConfigurePlatform");
- this.notificationChannels.SetNotificationChannelGroups(this.options.Android.NotificationChannelGroups);
- this.notificationChannels.SetNotificationChannels(this.options.Android.NotificationChannels);
+ var groups = this.options.Android.NotificationChannelGroups;
+ if (groups.Any())
+ {
+ this.notificationChannels.SetNotificationChannelGroups(groups);
+ }
+
+ var channels = this.options.Android.NotificationChannels;
+ if (channels.Any())
+ {
+ // If we have NotificationChannels set, use them to configure the absolute set of notification channels.
+ this.notificationChannels.SetNotificationChannels(channels);
+ }
+ else
+ {
+ // Otherwise, ensure we have at least the default notification channel.
+ ((Channels.NotificationChannels)this.notificationChannels).EnsureDefaultNotificationChannel();
+ }
var context = Application.Context;
var isFirebaseAppInitialized = FirebaseAppHelper.IsFirebaseAppInitialized(context);
@@ -122,11 +137,6 @@ private void InitializeFirebaseAppFromFirebaseOptions(Context context, FirebaseO
}
}
- protected override void OnNotificationReceived(IDictionary data)
- {
- this.NotificationBuilder?.OnNotificationReceived(data);
- }
-
public void ProcessIntent(Activity activity, Intent intent)
{
if (activity == null)
@@ -153,10 +163,6 @@ public void ProcessIntent(Activity activity, Intent intent)
return;
}
- var extras = intent.GetExtrasDict();
- this.logger.LogDebug(
- $"ProcessIntent: activity.Type={activityType.Name}, intent.Flags=[{intent.Flags}], intent.Extras=[{extras.ToDebugString()}]");
-
var launchedFromHistory = intent.Flags.HasFlag(ActivityFlags.LaunchedFromHistory);
if (launchedFromHistory)
{
@@ -164,6 +170,16 @@ public void ProcessIntent(Activity activity, Intent intent)
return;
}
+ if (string.Equals(intent.Action, Intent.ActionMain, StringComparison.InvariantCultureIgnoreCase))
+ {
+ // Don't process the intent if intent action is android.intent.action.MAIN
+ return;
+ }
+
+ var extras = intent.GetExtrasDict();
+ this.logger.LogDebug(
+ $"ProcessIntent: activity.Type={activityType.Name}, intent.Flags=[{intent.Flags}], intent.Extras=[{extras.ToDebugString()}]");
+
if (extras.Any())
{
// Don't process old/historic intents which are recycled for whatever reason
@@ -171,7 +187,7 @@ public void ProcessIntent(Activity activity, Intent intent)
if (!intent.GetBooleanExtra(intentAlreadyHandledKey, false))
{
intent.PutExtra(intentAlreadyHandledKey, true);
- this.logger.LogDebug($"ProcessIntent: {intentAlreadyHandledKey} not present --> Process notification");
+ this.logger.LogDebug($"ProcessIntent: {intentAlreadyHandledKey} not present → Process notification");
if (extras.TryGetInt(Constants.ActionNotificationIdKey, out var notificationId))
{
@@ -195,13 +211,12 @@ public void ProcessIntent(Activity activity, Intent intent)
else
{
var notificationActionId = extras.GetStringOrDefault(Constants.NotificationActionId);
- this.HandleNotificationAction(extras, notificationCategoryId, notificationActionId,
- NotificationCategoryType.Default);
+ this.HandleNotificationAction(extras, notificationCategoryId, notificationActionId, NotificationCategoryType.Default);
}
}
else
{
- this.logger.LogDebug($"ProcessIntent: {intentAlreadyHandledKey} is present --> Notification already processed");
+ this.logger.LogDebug($"ProcessIntent: {intentAlreadyHandledKey} is present → Notification already processed");
}
}
}
@@ -276,16 +291,16 @@ public string Token
public INotificationBuilder NotificationBuilder { get; set; }
///
- public void SubscribeTopics(string[] topics)
+ public async Task SubscribeTopicsAsync(string[] topics)
{
- foreach (var t in topics)
+ foreach (var topic in topics)
{
- this.SubscribeTopic(t);
+ await this.SubscribeTopicAsync(topic);
}
}
///
- public void SubscribeTopic(string topic)
+ public async Task SubscribeTopicAsync(string topic)
{
if (topic == null)
{
@@ -300,10 +315,12 @@ public void SubscribeTopic(string topic)
var subscribedTopics = new HashSet(this.SubscribedTopics);
if (!subscribedTopics.Contains(topic))
{
- this.logger.LogDebug($"Subscribe: topic=\"{topic}\"");
+ this.logger.LogDebug($"SubscribeTopicAsync: topic=\"{topic}\"");
- // TODO: Use AddOnCompleteListener(...)
- FirebaseMessaging.Instance.SubscribeToTopic(topic);
+ var tcs = new TaskCompletionSource();
+ var taskCompleteListener = new TaskCompleteListener(tcs);
+ FirebaseMessaging.Instance.SubscribeToTopic(topic).AddOnCompleteListener(taskCompleteListener);
+ await tcs.Task;
subscribedTopics.Add(topic);
@@ -312,12 +329,12 @@ public void SubscribeTopic(string topic)
}
else
{
- this.logger.LogInformation($"Subscribe: skipping topic \"{topic}\"; topic is already subscribed");
+ this.logger.LogInformation($"SubscribeTopicAsync: skipping topic \"{topic}\"; topic is already subscribed");
}
}
///
- public void UnsubscribeTopics(string[] topics)
+ public async Task UnsubscribeTopicsAsync(string[] topics)
{
if (topics == null)
{
@@ -325,26 +342,31 @@ public void UnsubscribeTopics(string[] topics)
}
// TODO: Improve efficiency here (move to base class maybe)
- foreach (var t in topics)
+ foreach (var topic in topics)
{
- this.UnsubscribeTopic(t);
+ await this.UnsubscribeTopicAsync(topic);
}
}
///
- public void UnsubscribeAllTopics()
+ public async Task UnsubscribeAllTopicsAsync()
{
+ var topics = this.SubscribedTopics.ToArray();
+ this.logger.LogDebug($"UnsubscribeAllTopicsAsync: topics=[{string.Join(',', topics)}]");
+
foreach (var topic in this.SubscribedTopics)
{
- // TODO: Use AddOnCompleteListener(...)
- FirebaseMessaging.Instance.UnsubscribeFromTopic(topic);
+ var tcs = new TaskCompletionSource();
+ var taskCompleteListener = new TaskCompleteListener(tcs);
+ FirebaseMessaging.Instance.UnsubscribeFromTopic(topic).AddOnCompleteListener(taskCompleteListener);
+ await tcs.Task;
}
this.SubscribedTopics = null;
}
///
- public void UnsubscribeTopic(string topic)
+ public async Task UnsubscribeTopicAsync(string topic)
{
if (topic == null)
{
@@ -359,10 +381,13 @@ public void UnsubscribeTopic(string topic)
var subscribedTopics = new HashSet(this.SubscribedTopics);
if (subscribedTopics.Contains(topic))
{
- this.logger.LogDebug($"Unsubscribe: topic=\"{topic}\"");
+ this.logger.LogDebug($"UnsubscribeTopicAsync: topic=\"{topic}\"");
+
+ var tcs = new TaskCompletionSource();
+ var taskCompleteListener = new TaskCompleteListener(tcs);
+ FirebaseMessaging.Instance.UnsubscribeFromTopic(topic).AddOnCompleteListener(taskCompleteListener);
+ await tcs.Task;
- // TODO: Use AddOnCompleteListener(...)
- FirebaseMessaging.Instance.UnsubscribeFromTopic(topic);
subscribedTopics.Remove(topic);
// TODO: Improve write performance here; don't loop all topics one by one
@@ -370,25 +395,27 @@ public void UnsubscribeTopic(string topic)
}
else
{
- this.logger.LogInformation($"Unsubscribe: skipping topic \"{topic}\"; topic is not subscribed");
+ this.logger.LogInformation($"UnsubscribeTopicAsync: skipping topic \"{topic}\"; topic is not subscribed");
}
}
protected override void HandleTokenRefreshPlatform(string token)
{
- this.ResubscribeExistingTopics();
+ _ = this.ResubscribeExistingTopicsAsync();
}
///
/// Resubscribes all existing topics since the old instance id isn't valid anymore.
/// This is obviously necessary but seems a very bad design decision...
///
- private void ResubscribeExistingTopics()
+ private async Task ResubscribeExistingTopicsAsync()
{
foreach (var topic in this.SubscribedTopics)
{
- // TODO: Use AddOnCompleteListener(...)
- FirebaseMessaging.Instance.SubscribeToTopic(topic);
+ var tcs = new TaskCompletionSource();
+ var taskCompleteListener = new TaskCompleteListener(tcs);
+ FirebaseMessaging.Instance.SubscribeToTopic(topic).AddOnCompleteListener(taskCompleteListener);
+ await tcs.Task;
}
}
diff --git a/Plugin.FirebasePushNotifications/Platforms/Android/ILLink.Descriptors.xml b/Plugin.FirebasePushNotifications/Platforms/Android/ILLink.Descriptors.xml
new file mode 100644
index 00000000..5a86e3db
--- /dev/null
+++ b/Plugin.FirebasePushNotifications/Platforms/Android/ILLink.Descriptors.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/Plugin.FirebasePushNotifications/Platforms/Android/NotificationBuilder.cs b/Plugin.FirebasePushNotifications/Platforms/Android/NotificationBuilder.cs
index 88b09416..b7d39a4b 100644
--- a/Plugin.FirebasePushNotifications/Platforms/Android/NotificationBuilder.cs
+++ b/Plugin.FirebasePushNotifications/Platforms/Android/NotificationBuilder.cs
@@ -45,7 +45,7 @@ public virtual bool ShouldHandleNotificationReceived(IDictionary
// we don't display any local notification.
this.logger.LogDebug(
$"ShouldHandleNotificationReceived returns false " +
- $"(Reason: Key '{MessageNotificationKeys.NoUi}' is present)");
+ $"(Reason: Data key '{MessageNotificationKeys.NoUi}' is present)");
return false;
}
@@ -55,99 +55,52 @@ public virtual bool ShouldHandleNotificationReceived(IDictionary
// we don't display any local notification.
this.logger.LogDebug(
$"ShouldHandleNotificationReceived returns false " +
- $"(Reason: Key '{Constants.SilentKey}' is present)");
+ $"(Reason: Data key '{Constants.SilentKey}' is present)");
return false;
}
- if (notificationParams.IsNotification)
- {
- var isAppInBackground = !AppHelper.IsAppForeground(Application.Context);
- if (isAppInBackground)
- {
- return true;
- }
- }
+ var isAppInForeground = AppHelper.IsAppForeground(Application.Context);
+ var isAppInBackground = !isAppInForeground;
- var notificationImportance = GetNotificationImportance(data);
- if (notificationImportance >= NotificationImportance.High)
+ if (!notificationParams.IsNotification)
{
- // In case we receive a notification with priority >= high
- // we show it in a local notification popup.
this.logger.LogDebug(
- $"ShouldHandleNotificationReceived returns true " +
- $"(Reason: Notification importance '{notificationImportance}' is greater or equal to 'high')");
- return true;
+ $"ShouldHandleNotificationReceived returns false " +
+ $"(Reason: Data-only notification)");
+ return false;
}
- var defaultNotificationImportance = this.options.Android.DefaultNotificationImportance;
- if (defaultNotificationImportance >= NotificationImportance.High)
+ if (isAppInBackground)
{
- // In case a default notification importance >= high is configured
- // we show it in a local notification popup.
this.logger.LogDebug(
$"ShouldHandleNotificationReceived returns true " +
- $"(Reason: Default notification importance '{defaultNotificationImportance}' is greater or equal to 'high')");
+ $"(Reason: App runs in background mode)");
return true;
}
- var presentClickActionKeys = Constants.ClickActionKeys
- .Where(data.ContainsKey)
- .ToArray();
-
- if (presentClickActionKeys.Length > 0)
- {
- var isAppInBackground = !AppHelper.IsAppForeground(Application.Context);
- if (isAppInBackground)
- {
- // If we received a "click_action" or "category"
- // and we run in background mode
- // we need to show a local notification with action buttons.
- this.logger.LogDebug(
- $"ShouldHandleNotificationReceived returns true " +
- $"(Reason: {(presentClickActionKeys.Length == 1 ?
- $"Key '{presentClickActionKeys.Single()}' is present" :
- $"Keys [{string.Join(",", presentClickActionKeys)}] are present")})");
- return true;
- }
- }
-
- var notificationChannel = this.GetNotificationChannel(data);
- if (notificationChannel is { Importance: >= NotificationImportance.High })
+ var notificationImportance = GetNotificationImportance(data);
+ if (notificationImportance >= NotificationImportance.High)
{
- // In case we receive a notification which targets a specific notification channel
- // and the notification channel's importance is >= high
- // we show it in a local notification popup.
this.logger.LogDebug(
$"ShouldHandleNotificationReceived returns true " +
- $"(Reason: Target notification channel '{notificationChannel.Id}' " +
- $"has importance '{notificationChannel.Importance}' greater or equal to 'high')");
+ $"(Reason: Notification importance '{notificationImportance}' is higher than or equal to 'High')");
return true;
}
- if (data.ContainsKey(Constants.LargeIconKey))
- {
- // If we received a "large_icon"
- // we need to show a local notification with SetLargeIcon
- this.logger.LogDebug(
- $"ShouldHandleNotificationReceived returns true " +
- $"(Reason: Key '{Constants.LargeIconKey}' present)");
- return true;
- }
+ // if (data.ContainsKey(Constants.LargeIconKey))
+ // {
+ // // If we received a "large_icon"
+ // // we need to show a local notification with SetLargeIcon
+ // this.logger.LogDebug(
+ // $"ShouldHandleNotificationReceived returns true " +
+ // $"(Reason: Key '{Constants.LargeIconKey}' present)");
+ // return true;
+ // }
this.logger.LogDebug("ShouldHandleNotificationReceived returns false");
return false;
}
- void INotificationBuilder.OnNotificationReceived(IDictionary data)
- {
- if (!this.ShouldHandleNotificationReceived(data))
- {
- return;
- }
-
- this.OnNotificationReceived(data);
- }
-
///
/// This method is called if we have to build our own, custom notification using NotificationCompat.Builder.
///
@@ -170,12 +123,13 @@ public virtual void OnNotificationReceived(IDictionary data)
extras.PutString(kvp.Key, kvp.Value.ToString());
}
- var notificationId = this.GetNotificationId(data);
+ var notificationId = GetNotificationId(data);
extras.PutInt(Constants.ActionNotificationIdKey, notificationId);
- if (data.TryGetString(Constants.NotificationTagKey, out var tag))
+ var notificationTag = GetNotificationTag(data);
+ if (notificationTag != null)
{
- extras.PutString(Constants.ActionNotificationTagKey, tag);
+ extras.PutString(Constants.ActionNotificationTagKey, notificationTag);
}
var context = Application.Context;
@@ -187,22 +141,36 @@ public virtual void OnNotificationReceived(IDictionary data)
launchIntent.SetFlags(activityFlags);
}
- var notificationChannel = this.GetNotificationChannelOrDefault(data);
- if (notificationChannel == null)
+ NotificationChannel notificationChannel;
+ string notificationChannelId;
+ if (Build.VERSION.SdkInt < BuildVersionCodes.O)
+ {
+ notificationChannel = null;
+ notificationChannelId = NotificationChannel.DefaultChannelId;
+ }
+ else
{
- this.logger.LogError(
- $"NotificationCompat.Builder requires a notification channel to work properly. " +
- $"Use {nameof(INotificationChannels)}.{nameof(INotificationChannels.CreateNotificationChannels)} " +
- $"to create at least one notification channel.");
- return;
+ notificationChannel = this.GetNotificationChannelOrDefault(data);
+ if (notificationChannel == null)
+ {
+ this.logger.LogError(
+ $"NotificationCompat.Builder requires a notification channel to work properly. " +
+ $"Use {nameof(INotificationChannels)}.{nameof(INotificationChannels.CreateNotificationChannels)} or " +
+ $"{nameof(INotificationChannels)}.{nameof(INotificationChannels.SetNotificationChannels)} " +
+ $"to create at least one notification channel.");
+ return;
+ }
+
+ notificationChannelId = notificationChannel.Id;
}
- var notificationImportance = this.GetNotificationImportanceOrDefault(data);
- if (notificationChannel.Importance < notificationImportance)
+ var notificationImportance = GetNotificationImportance(data);
+ if (notificationChannel is { Importance: var notificationChannelImportance } &&
+ notificationChannelImportance < notificationImportance)
{
this.logger.LogWarning(
- $"Notification channel '{notificationChannel.Id}' has importance '{notificationChannel.Importance}' " +
- $"which is lower than notification importance '{notificationImportance}'");
+ $"Notification channel with Id={notificationChannelId} has Importance={notificationChannelImportance} " +
+ $"which is lower than '{notificationImportance}'.");
}
var smallIconResource = this.GetSmallIconResource(data, context);
@@ -213,7 +181,7 @@ public virtual void OnNotificationReceived(IDictionary data)
var pendingIntent = PendingIntent.GetActivity(context, requestCode, launchIntent,
PendingIntentFlags.UpdateCurrent | PendingIntentFlags.Immutable);
- var notificationBuilder = new NotificationCompat.Builder(context, notificationChannel.Id)
+ var notificationBuilder = new NotificationCompat.Builder(context, notificationChannelId)
.SetSmallIcon(smallIconResource)
.SetAutoCancel(true)
.SetWhen(Java.Lang.JavaSystem.CurrentTimeMillis())
@@ -255,10 +223,12 @@ public virtual void OnNotificationReceived(IDictionary data)
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
{
- var notificationPriority = GetNotificationPriority(notificationImportance);
+ // SetPriority was deprecated in API level 26.
+ var notificationImportanceOrDefault = notificationImportance ?? this.options.Android.DefaultNotificationImportance;
+ var notificationPriority = GetNotificationPriority(notificationImportanceOrDefault);
notificationBuilder.SetPriority(notificationPriority);
- var notificationVibrationPattern = GetNotificationVibrationPattern(notificationImportance);
+ var notificationVibrationPattern = GetNotificationVibrationPattern(notificationImportanceOrDefault);
if (notificationVibrationPattern != null)
{
notificationBuilder.SetVibrate(notificationVibrationPattern);
@@ -266,6 +236,7 @@ public virtual void OnNotificationReceived(IDictionary data)
try
{
+ // SetSound was deprecated in API level 26.
var soundUri = this.GetSoundUri(data, context);
notificationBuilder.SetSound(soundUri);
}
@@ -335,8 +306,8 @@ public virtual void OnNotificationReceived(IDictionary data)
PendingIntentFlags.UpdateCurrent | PendingIntentFlags.Immutable);
}
- var icon = context.Resources.GetIdentifier(notificationAction.Icon ?? "", "drawable", context.PackageName);
- var action = new NotificationCompat.Action.Builder(icon, notificationAction.Title, pendingActionIntent).Build();
+ var iconResource = this.GetIconResourceFromDrawableOrMipmap(context, notificationAction.Icon);
+ var action = new NotificationCompat.Action.Builder(iconResource, notificationAction.Title, pendingActionIntent).Build();
notificationBuilder.AddAction(action);
}
}
@@ -360,13 +331,13 @@ public virtual void OnNotificationReceived(IDictionary data)
var notificationManager = (NotificationManager)context.GetSystemService(Context.NotificationService);
var notification = notificationBuilder.Build();
- if (tag == null)
+ if (notificationTag == null)
{
notificationManager.Notify(notificationId, notification);
}
else
{
- notificationManager.Notify(tag, notificationId, notification);
+ notificationManager.Notify(notificationTag, notificationId, notification);
}
}
@@ -470,12 +441,7 @@ private static long[] GetNotificationVibrationPattern(NotificationImportance not
private Bitmap GetLargeIconBitmap(IDictionary data, Context context)
{
- var largeIconResource = this.GetIconResourceFromDrawable(context, data, Constants.LargeIconKey);
-
- if (largeIconResource == 0)
- {
- largeIconResource = this.GetIconResourceFromMipmap(context, data, Constants.LargeIconKey);
- }
+ var largeIconResource = this.GetIconResourceFromDrawableOrMipmap(context, data, Constants.LargeIconKey);
if (largeIconResource == 0 &&
data.TryGetString(Constants.LargeIconKey, out var largeIconUrl) &&
@@ -539,12 +505,7 @@ private Bitmap DownloadBitmap(string url)
private int GetSmallIconResource(IDictionary data, Context context)
{
- var smallIconResource = this.GetIconResourceFromDrawable(context, data, Constants.IconKey);
-
- if (smallIconResource == 0)
- {
- smallIconResource = this.GetIconResourceFromMipmap(context, data, Constants.IconKey);
- }
+ var smallIconResource = this.GetIconResourceFromDrawableOrMipmap(context, data, Constants.IconKey);
if (smallIconResource == 0 && this.options.Android.DefaultIconResource is int defaultIconResource)
{
@@ -620,48 +581,55 @@ private string TryGetResourceName(Context context, int resid, string residName,
return resourceName;
}
- private static int GetIconResourceFromDrawableOrMipmap___(Context context, IDictionary data, string dataKey)
+ private int GetIconResourceFromDrawableOrMipmap(Context context, IDictionary data, string iconKey)
{
- var iconResourceId = 0;
+ var iconResource = 0;
- if (data.TryGetString(dataKey, out var iconKey) && iconKey != null)
+ if (data.TryGetString(iconKey, out var iconName))
{
- iconResourceId = context.Resources.GetIdentifier(iconKey, "drawable", context.PackageName);
-
- if (iconResourceId == 0)
- {
- iconResourceId = context.Resources.GetIdentifier(iconKey, "mipmap", context.PackageName);
- }
+ iconResource = this.GetIconResourceFromDrawableOrMipmap(context, iconName);
}
- return iconResourceId;
+ return iconResource;
}
- private int GetIconResourceFromDrawable(Context context, IDictionary data, string dataKey)
+ private int GetIconResourceFromDrawableOrMipmap(Context context, string iconName)
{
- return this.GetIconResource(context, data, dataKey, "drawable");
+ if (string.IsNullOrEmpty(iconName))
+ {
+ return 0;
+ }
+
+ var iconResource = this.GetIconResourceFromDrawable(context, iconName);
+
+ if (iconResource == 0)
+ {
+ iconResource = this.GetIconResourceFromMipmap(context, iconName);
+ }
+
+ return iconResource;
}
- private int GetIconResourceFromMipmap(Context context, IDictionary data, string dataKey)
+ private int GetIconResourceFromMipmap(Context context, string iconName)
{
- return this.GetIconResource(context, data, dataKey, "mipmap");
+ return this.GetIconResource(context, iconName, "mipmap");
}
- private int GetIconResource(Context context, IDictionary data, string dataKey, string defType)
+ private int GetIconResourceFromDrawable(Context context, string iconName)
{
- var iconResourceId = 0;
+ return this.GetIconResource(context, iconName, "drawable");
+ }
- if (data.TryGetString(dataKey, out var iconKey) && iconKey != null)
+ private int GetIconResource(Context context, string iconName, string defType)
+ {
+ var resourceId = context.Resources.GetIdentifier(iconName, defType, context.PackageName);
+ var resourceName = this.TryGetResourceName(context, resourceId, nameof(resourceId), nameof(this.GetIconResource), defType);
+ if (resourceName == null)
{
- iconResourceId = context.Resources.GetIdentifier(iconKey, defType, context.PackageName);
- var resourceName = this.TryGetResourceName(context, iconResourceId, nameof(iconResourceId), nameof(this.GetIconResource), defType);
- if (resourceName == null)
- {
- iconResourceId = 0;
- }
+ resourceId = 0;
}
- return iconResourceId;
+ return resourceId;
}
private int? GetNotificationColor(IDictionary data)
@@ -760,27 +728,18 @@ private NotificationChannel GetNotificationChannel(IDictionary d
return notificationChannel;
}
- private int GetNotificationId(IDictionary data)
+ private static int GetNotificationId(IDictionary data)
{
- var notificationId = 0;
-
- // TODO: Use TryGetInt here
- if (data.TryGetString(Constants.IdKey, out var id))
- {
- try
- {
- notificationId = Convert.ToInt32(id);
- }
- catch (Exception ex)
- {
- // Keep the default value of zero for the notify_id, but log the conversion problem.
- this.logger.LogError(ex, $"Failed to convert {id} to an integer");
- }
- }
-
+ data.TryGetInt(Constants.IdKey, out var notificationId);
return notificationId;
}
+ private static string GetNotificationTag(IDictionary data)
+ {
+ data.TryGetString(Constants.NotificationTagKey, out var notificationTag);
+ return notificationTag;
+ }
+
private static NotificationImportance? GetNotificationImportance(IDictionary data)
{
NotificationImportance? notificationImportance = null;
@@ -793,14 +752,21 @@ private int GetNotificationId(IDictionary data)
return notificationImportance;
}
- private NotificationImportance GetNotificationImportanceOrDefault(IDictionary data)
+ private (NotificationImportance, string) GetNotificationImportanceOrDefault(IDictionary data)
{
+ string notificationImportanceSource;
+
if (GetNotificationImportance(data) is not NotificationImportance notificationImportance)
{
notificationImportance = this.options.Android.DefaultNotificationImportance;
+ notificationImportanceSource = nameof(this.options.Android.DefaultNotificationImportance);
+ }
+ else
+ {
+ notificationImportanceSource = $"notification '{Constants.PriorityKey}' flag";
}
- return notificationImportance;
+ return (notificationImportance, notificationImportanceSource);
}
private static NotificationImportance GetNotificationImportance(string priorityValue)
@@ -918,8 +884,7 @@ private static string GetCategoryValue(IDictionary data)
///
/// Notification builder.
/// Data payload.
- private void ResolveLocalizedParameters(NotificationCompat.Builder notificationBuilder,
- IDictionary data)
+ private void ResolveLocalizedParameters(NotificationCompat.Builder notificationBuilder, IDictionary data)
{
// Resolve title localization
if (data.TryGetString("title_loc_key", out var titleKey))
@@ -946,8 +911,7 @@ private void ResolveLocalizedParameters(NotificationCompat.Builder notificationB
}
}
- private string GetLocalizedString(string name, string[] arguments,
- NotificationCompat.Builder notificationBuilder)
+ private string GetLocalizedString(string name, string[] arguments, NotificationCompat.Builder notificationBuilder)
{
try
{
diff --git a/Plugin.FirebasePushNotifications/Platforms/Android/NotificationPermissions.cs b/Plugin.FirebasePushNotifications/Platforms/Android/NotificationPermissions.cs
index a7951aff..6a316b53 100644
--- a/Plugin.FirebasePushNotifications/Platforms/Android/NotificationPermissions.cs
+++ b/Plugin.FirebasePushNotifications/Platforms/Android/NotificationPermissions.cs
@@ -103,7 +103,7 @@ private bool IsAnyChannelBlocked(string[] channelIds)
foreach (var channelId in channelIds)
{
var channel = this.notificationManager.GetNotificationChannel(channelId);
- if (channel != null && channel.Importance == NotificationImportance.None)
+ if (channel is { Importance: NotificationImportance.None })
{
return true;
}
diff --git a/Plugin.FirebasePushNotifications/Platforms/Android/Options/FirebasePushNotificationAndroidOptions.cs b/Plugin.FirebasePushNotifications/Platforms/Android/Options/FirebasePushNotificationAndroidOptions.cs
index bf0cfa48..39e7ce46 100644
--- a/Plugin.FirebasePushNotifications/Platforms/Android/Options/FirebasePushNotificationAndroidOptions.cs
+++ b/Plugin.FirebasePushNotifications/Platforms/Android/Options/FirebasePushNotificationAndroidOptions.cs
@@ -79,12 +79,12 @@ public virtual NotificationChannelRequest[] NotificationChannels
public string NotificationBodyKey { get; set; }
///
- /// The notification importance used by default
- /// - if the notification data does not contain any "priority" flags.
- /// - as notification importance for the default notification channel (if not specified).
- /// Default value: NotificationImportance.Default
+ /// The notification importance used in following cases:
+ /// - If the notification data does not contain any "priority" flags.
+ /// - As notification importance for the default notification channel.
+ /// Default value: NotificationImportance.High
///
- public NotificationImportance DefaultNotificationImportance { get; set; } = NotificationImportance.Default;
+ public NotificationImportance DefaultNotificationImportance { get; set; } = NotificationImportance.High;
public int? DefaultIconResource { get; set; }
diff --git a/Plugin.FirebasePushNotifications/Platforms/Android/PNFirebaseMessagingService.cs b/Plugin.FirebasePushNotifications/Platforms/Android/PNFirebaseMessagingService.cs
index 3130a4b6..2e40ee08 100644
--- a/Plugin.FirebasePushNotifications/Platforms/Android/PNFirebaseMessagingService.cs
+++ b/Plugin.FirebasePushNotifications/Platforms/Android/PNFirebaseMessagingService.cs
@@ -3,6 +3,7 @@
using Android.OS;
using Firebase.Messaging;
using Microsoft.Extensions.Logging;
+using Plugin.FirebasePushNotifications.Utils;
namespace Plugin.FirebasePushNotifications.Platforms
{
@@ -124,17 +125,22 @@ private void DispatchMessage(Intent intent)
var data = intent.GetExtrasDict();
data.Remove("com.google.firebase.iid.WakeLockHolder.wakefulintent");
- if (this.notificationBuilder.ShouldHandleNotificationReceived(data))
+ var handleNotification = this.notificationBuilder.ShouldHandleNotificationReceived(data);
+
+ var isAppInForeground = AppHelper.IsAppForeground();
+ if (isAppInForeground || handleNotification == false)
{
- this.notificationBuilder.OnNotificationReceived(data);
+ this.firebasePushNotification.HandleNotificationReceived(data);
}
- else
+
+ if (handleNotification)
{
- this.firebasePushNotification.HandleNotificationReceived(data);
+ this.notificationBuilder.OnNotificationReceived(data);
}
}
// TODO: Check if this code is still needed or if it can be removed.
+ /*
private void OnMessageReceived(RemoteMessage remoteMessage)
{
this.logger.LogDebug("OnMessageReceived");
@@ -237,7 +243,7 @@ private void OnMessageReceived(RemoteMessage remoteMessage)
}
this.firebasePushNotification.HandleNotificationReceived(data);
- }
+ }*/
private void HandleTokenIntent(Intent intent)
{
diff --git a/Plugin.FirebasePushNotifications/Platforms/Android/PushNotificationActionReceiver.cs b/Plugin.FirebasePushNotifications/Platforms/Android/PushNotificationActionReceiver.cs
index 91b88a16..ff8c1040 100644
--- a/Plugin.FirebasePushNotifications/Platforms/Android/PushNotificationActionReceiver.cs
+++ b/Plugin.FirebasePushNotifications/Platforms/Android/PushNotificationActionReceiver.cs
@@ -28,18 +28,17 @@ public override void OnReceive(Context context, Intent intent)
var firebasePushNotification = IFirebasePushNotification.Current;
firebasePushNotification.HandleNotificationAction(extras, notificationCategoryId, notificationActionId, NotificationCategoryType.Default);
- var manager = context.GetSystemService(Context.NotificationService) as NotificationManager;
- var notificationId = extras.GetValueOrDefault(Constants.ActionNotificationIdKey, -1);
- if (notificationId != -1)
+ var notificationManager = context.GetSystemService(Context.NotificationService) as NotificationManager;
+ if (extras.TryGetInt(Constants.ActionNotificationIdKey, out var notificationId))
{
var notificationTag = extras.GetStringOrDefault(Constants.ActionNotificationTagKey, null);
if (notificationTag == null)
{
- manager.Cancel(notificationId);
+ notificationManager.Cancel(notificationId);
}
else
{
- manager.Cancel(notificationTag, notificationId);
+ notificationManager.Cancel(notificationTag, notificationId);
}
}
}
diff --git a/Plugin.FirebasePushNotifications/Platforms/Android/proguard.cfg b/Plugin.FirebasePushNotifications/Platforms/Android/proguard.cfg
new file mode 100644
index 00000000..04a0f103
--- /dev/null
+++ b/Plugin.FirebasePushNotifications/Platforms/Android/proguard.cfg
@@ -0,0 +1,6 @@
+-dontwarn com.google.android.gms.**
+-keep class com.google.android.gms.** { *; }
+-keep class com.google.firebase.** { *; }
+-keep class androidx.startup.AppInitializer
+-keep class androidx.startup.InitializationProvider
+-keep class androidx.startup.Initializer
\ No newline at end of file
diff --git a/Plugin.FirebasePushNotifications/Platforms/iOS/FirebasePushNotificationManager.cs b/Plugin.FirebasePushNotifications/Platforms/iOS/FirebasePushNotificationManager.cs
index 0602f5d6..10e52fbc 100644
--- a/Plugin.FirebasePushNotifications/Platforms/iOS/FirebasePushNotificationManager.cs
+++ b/Plugin.FirebasePushNotifications/Platforms/iOS/FirebasePushNotificationManager.cs
@@ -311,8 +311,7 @@ public void DidReceiveRemoteNotification(NSDictionary userInfo)
}
///
- public void DidReceiveRemoteNotification(UIApplication application, NSDictionary userInfo,
- Action completionHandler)
+ public void DidReceiveRemoteNotification(UIApplication application, NSDictionary userInfo, Action completionHandler)
{
this.logger.LogDebug("DidReceiveRemoteNotification(UIApplication, NSDictionary, Action)");
@@ -334,8 +333,7 @@ private void DidReceiveRemoteNotificationInternal(NSDictionary userInfo)
this.HandleNotificationReceived(data);
}
- private void WillPresentNotification(UNUserNotificationCenter center, UNNotification notification,
- Action completionHandler)
+ private void WillPresentNotification(UNUserNotificationCenter center, UNNotification notification, Action completionHandler)
{
if (OperatingSystem.IsIOSVersionAtLeast(18))
{
@@ -361,11 +359,9 @@ private void WillPresentNotification(UNUserNotificationCenter center, UNNotifica
completionHandler(notificationPresentationOptions);
}
- private static UNNotificationPresentationOptions GetNotificationPresentationOptions(
- IDictionary data,
- UNNotificationPresentationOptions defaultNotificationPresentationOptions)
+ private static UNNotificationPresentationOptions GetNotificationPresentationOptions(IDictionary data, UNNotificationPresentationOptions notificationPresentationOptions)
{
- var notificationPresentationOptions = defaultNotificationPresentationOptions;
+ var options = notificationPresentationOptions;
var priority = GetPriorityValue(data);
if (!string.IsNullOrEmpty(priority))
@@ -374,21 +370,21 @@ private static UNNotificationPresentationOptions GetNotificationPresentationOpti
{
if (UIDevice.CurrentDevice.CheckSystemVersion(14, 0))
{
- if (!notificationPresentationOptions.HasFlag(UNNotificationPresentationOptions.List))
+ if (!options.HasFlag(UNNotificationPresentationOptions.List))
{
- notificationPresentationOptions |= UNNotificationPresentationOptions.List;
+ options |= UNNotificationPresentationOptions.List;
}
- if (!notificationPresentationOptions.HasFlag(UNNotificationPresentationOptions.Banner))
+ if (!options.HasFlag(UNNotificationPresentationOptions.Banner))
{
- notificationPresentationOptions |= UNNotificationPresentationOptions.Banner;
+ options |= UNNotificationPresentationOptions.Banner;
}
}
else
{
- if (!notificationPresentationOptions.HasFlag(UNNotificationPresentationOptions.Alert))
+ if (!options.HasFlag(UNNotificationPresentationOptions.Alert))
{
- notificationPresentationOptions |= UNNotificationPresentationOptions.Alert;
+ options |= UNNotificationPresentationOptions.Alert;
}
}
}
@@ -396,27 +392,27 @@ private static UNNotificationPresentationOptions GetNotificationPresentationOpti
{
if (UIDevice.CurrentDevice.CheckSystemVersion(14, 0))
{
- if (notificationPresentationOptions.HasFlag(UNNotificationPresentationOptions.List))
+ if (options.HasFlag(UNNotificationPresentationOptions.List))
{
- notificationPresentationOptions &= ~UNNotificationPresentationOptions.List;
+ options &= ~UNNotificationPresentationOptions.List;
}
- if (notificationPresentationOptions.HasFlag(UNNotificationPresentationOptions.Banner))
+ if (options.HasFlag(UNNotificationPresentationOptions.Banner))
{
- notificationPresentationOptions &= ~UNNotificationPresentationOptions.Banner;
+ options &= ~UNNotificationPresentationOptions.Banner;
}
}
else
{
- if (notificationPresentationOptions.HasFlag(UNNotificationPresentationOptions.Alert))
+ if (options.HasFlag(UNNotificationPresentationOptions.Alert))
{
- notificationPresentationOptions &= ~UNNotificationPresentationOptions.Alert;
+ options &= ~UNNotificationPresentationOptions.Alert;
}
}
}
}
- return notificationPresentationOptions;
+ return options;
}
private static string GetPriorityValue(IDictionary data)
@@ -437,16 +433,16 @@ private static string GetPriorityValue(IDictionary data)
}
///
- public void SubscribeTopics(string[] topics)
+ public async Task SubscribeTopicsAsync(string[] topics)
{
- foreach (var t in topics)
+ foreach (var topic in topics)
{
- this.SubscribeTopic(t);
+ await this.SubscribeTopicAsync(topic);
}
}
///
- public void SubscribeTopic(string topic)
+ public async Task SubscribeTopicAsync(string topic)
{
if (topic == null)
{
@@ -467,36 +463,36 @@ public void SubscribeTopic(string topic)
var subscribedTopics = new HashSet(this.SubscribedTopics);
if (!subscribedTopics.Contains(topic))
{
- this.logger.LogDebug($"Subscribe: topic=\"{topic}\"");
+ this.logger.LogDebug($"SubscribeTopicAsync: topic=\"{topic}\"");
- Firebase.CloudMessaging.Messaging.SharedInstance.Subscribe(topic);
+ await Firebase.CloudMessaging.Messaging.SharedInstance.SubscribeAsync(topic);
subscribedTopics.Add(topic);
this.SubscribedTopics = subscribedTopics.ToArray();
}
else
{
- this.logger.LogInformation($"Subscribe: skipping topic \"{topic}\"; topic is already subscribed");
+ this.logger.LogInformation($"SubscribeTopicAsync: skipping topic \"{topic}\"; topic is already subscribed");
}
}
///
- public void UnsubscribeAllTopics()
+ public async Task UnsubscribeAllTopicsAsync()
{
var topics = this.SubscribedTopics.ToArray();
- this.logger.LogDebug($"UnsubscribeAllTopics: topics=[{string.Join(',', topics)}]");
+ this.logger.LogDebug($"UnsubscribeAllTopicsAsync: topics=[{string.Join(',', topics)}]");
foreach (var topic in topics)
{
- this.logger.LogDebug($"Unsubscribe: topic=\"{topic}\"");
- Firebase.CloudMessaging.Messaging.SharedInstance.Unsubscribe(topic);
+ this.logger.LogDebug($"UnsubscribeAsync: topic=\"{topic}\"");
+ await Firebase.CloudMessaging.Messaging.SharedInstance.UnsubscribeAsync(topic);
}
this.SubscribedTopics = null;
}
///
- public void UnsubscribeTopics(string[] topics)
+ public async Task UnsubscribeTopicsAsync(string[] topics)
{
if (topics == null)
{
@@ -504,14 +500,14 @@ public void UnsubscribeTopics(string[] topics)
}
// TODO: Improve efficiency here (move to base class maybe)
- foreach (var t in topics)
+ foreach (var topic in topics)
{
- this.UnsubscribeTopic(t);
+ await this.UnsubscribeTopicAsync(topic);
}
}
///
- public void UnsubscribeTopic(string topic)
+ public async Task UnsubscribeTopicAsync(string topic)
{
if (topic == null)
{
@@ -532,16 +528,16 @@ public void UnsubscribeTopic(string topic)
var subscribedTopics = new HashSet(this.SubscribedTopics);
if (subscribedTopics.Contains(topic))
{
- this.logger.LogDebug($"Unsubscribe: topic=\"{topic}\"");
+ this.logger.LogDebug($"UnsubscribeTopicAsync: topic=\"{topic}\"");
- Firebase.CloudMessaging.Messaging.SharedInstance.Unsubscribe(topic);
+ await Firebase.CloudMessaging.Messaging.SharedInstance.UnsubscribeAsync(topic);
subscribedTopics.Remove(topic);
this.SubscribedTopics = subscribedTopics.ToArray();
}
else
{
- this.logger.LogInformation($"Unsubscribe: skipping topic \"{topic}\"; topic is not subscribed");
+ this.logger.LogInformation($"UnsubscribeTopicAsync: skipping topic \"{topic}\"; topic is not subscribed");
}
}
@@ -601,10 +597,10 @@ private void DidReceiveRegistrationToken(string fcmToken)
this.HandleTokenRefresh(fcmToken);
- this.TryDequeuePendingTopics();
+ _ = this.TryDequeuePendingTopicsAsync();
}
- private void TryDequeuePendingTopics()
+ private async Task TryDequeuePendingTopicsAsync()
{
if (!HasApnsToken)
{
@@ -615,11 +611,11 @@ private void TryDequeuePendingTopics()
{
if (pendingTopic.Subscribe)
{
- this.SubscribeTopic(pendingTopic.Topic);
+ await this.SubscribeTopicAsync(pendingTopic.Topic);
}
else
{
- this.UnsubscribeTopic(pendingTopic.Topic);
+ await this.UnsubscribeTopicAsync(pendingTopic.Topic);
}
}
}
@@ -646,6 +642,7 @@ public async void RemoveNotification(int id)
.Where(u => $"{u.Request.Content.UserInfo[notificationIdKey]}".Equals($"{id}"))
.Select(s => s.Request.Identifier)
.ToArray();
+
if (deliveredNotificationsMatches.Length > 0)
{
UNUserNotificationCenter.Current.RemoveDeliveredNotifications(deliveredNotificationsMatches);
diff --git a/Plugin.FirebasePushNotifications/Plugin.FirebasePushNotifications.csproj b/Plugin.FirebasePushNotifications/Plugin.FirebasePushNotifications.csproj
index 7a5359c8..ced1bafb 100644
--- a/Plugin.FirebasePushNotifications/Plugin.FirebasePushNotifications.csproj
+++ b/Plugin.FirebasePushNotifications/Plugin.FirebasePushNotifications.csproj
@@ -1,18 +1,18 @@
- net7.0;net7.0-android33.0;net7.0-ios;net8.0;net8.0-android34.0;net8.0-ios17.0
+ net9.0;net9.0-android35.0;net9.0-ios18.0;net10.0;net10.0-android36.0;net10.0-ios26.0
Library
true
- 8.0.3
- 7.0.49
+ 9.0.0
+ 10.0.0
true
enable
disable
true
- True
+
- 12.0
+ 12.2
24.0
true
@@ -32,46 +32,18 @@
firebase;push;notification;notifications
logo.png
LICENSE
+ README.md
https://github.com/thomasgalliker/Plugin.FirebasePushNotifications
git
https://github.com/thomasgalliker/Plugin.FirebasePushNotifications
superdev GmbH
false
- 3.1
-- Extend INotificationChannels to manage notification channel groups.
-- Internal refactoring of INotificationChannels implementation.
-- Removed properties IsActive and IsDefault from NotificationChannelRequest. Set the default notification channel via UseFirebasePushNotifications(o => o.Android.DefaultNotificationChannelId = ...).
-- Configure initial list of notification channels via o.Android.NotificationChannels and notification groups via o.Android.NotificationChannelGroups.
-
-3.0
-- Update firebase-ios-sdk by replacing nuget package Xamarin.Firebase.iOS.CloudMessaging with AdamE.Firebase.iOS.CloudMessaging.
-
-2.5
-- Move static properties from Android's FirebasePushNotificationManager to FirebasePushNotificationAndroidOptions.
-- iOS 18 workaround for duplicate notifications in foreground mode.
-- iOS options to override default UNNotificationPresentationOptions for notifications received in foreground mode.
-- Handle gcm.notification.click_action payload as click_action in Android.
-
-2.4
-- Refactor instanciation of IFirebasePushNotification.
-- Refactor startup procedure of platform-specific services.
-- Add singleton instance INotificationPermissions.Current.
-
-2.3
-- General bug fixes and code cleanup.
-- Bug fixes in the area of topic subscriptions.
-- IFirebasePushNotification.Current.
-- Add singleton instance IFirebasePushNotification.Current and INotificationPermissions.Current.
-
-2.2
-- Complete refactoring of the original 1.x implementation.
-- Simplified APIs, less static code, support for dependency injection.
-
-1.0
-- Initial release.
-
- Copyright $([System.DateTime]::Now.ToString(`yyyy`)) © Thomas Galliker
- README.md
+ Copyright $([System.DateTime]::Now.ToString(`yyyy`)) © Thomas Galliker
+ $([System.IO.File]::ReadAllText("$(MSBuildProjectDirectory)/../ReleaseNotes.txt"))
+ true
+ snupkg
+ true
+ true
@@ -80,30 +52,43 @@
-
-
+
+ true
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
+
+
+
-
-
-
+
+
-
-
+
+
+
+
+
+
+
+
+
diff --git a/README.md b/README.md
index 4581fa95..38a5be99 100644
--- a/README.md
+++ b/README.md
@@ -1,25 +1,36 @@
# Plugin.FirebasePushNotifications
+
[](https://www.nuget.org/packages/Plugin.FirebasePushNotifications) [](https://www.nuget.org/packages/Plugin.FirebasePushNotifications) [](https://buymeacoffee.com/thomasgalliker)
-Plugin.FirebasePushNotifications provides a seamless way to engage users and keep them informed about important events in your .NET MAUI applications. This open-source C# library integrates Firebase Cloud Messaging (FCM) into your .NET MAUI projects, enabling you to receive push notifications effortlessly.
+Plugin.FirebasePushNotifications provides a seamless way to engage users and keep them informed about important events
+in your .NET MAUI applications. This open-source C# library integrates Firebase Cloud Messaging (FCM) into your .NET
+MAUI projects, enabling you to receive push notifications effortlessly.
+
+## Features
-### Features
-* Cross-platform Compatibility: Works seamlessly with .NET MAUI, ensuring a consistent push notification experience across different devices and platforms.
+* Cross-platform Compatibility: Works seamlessly with .NET MAUI, ensuring a consistent push notification experience
+ across different devices and platforms.
* Easy Integration: Simple setup process to incorporate Firebase Push Notifications into your .NET MAUI apps.
-* Flexible Messaging: Utilize FCM's powerful features, such as targeted messaging, to send notifications based on user segments or specific conditions.
+* Flexible Messaging: Utilize FCM's powerful features, such as targeted messaging, to send notifications based on user
+ segments or specific conditions.
+
+## Download and Install Plugin.FirebasePushNotifications
-### Download and Install Plugin.FirebasePushNotifications
This library is available on NuGet: https://www.nuget.org/packages/Plugin.FirebasePushNotifications
Use the following command to install Plugin.FirebasePushNotifications using NuGet package manager console:
PM> Install-Package Plugin.FirebasePushNotifications
-You can use this library in any .NET MAUI project compatible to .NET 7 and higher.
+## Setup
+
+### Setup Firebase Push Notifications
+
+- Go to https://console.firebase.google.com and create a new project. The setup of Firebase projects is not (yet?)
+ documented here. Contributors welcome!
+- You have to download the resulting Firebase service files and integrate them into your .NET MAUI csproj file.
+ `google-services.json` is used by Android while `GoogleService-Info.plist` is accessible to iOS. Make sure the Include
+ and the Link paths match.
-### Setup
-#### Setup Firebase Push Notifications
-- Go to https://console.firebase.google.com and create a new project. The setup of Firebase projects is not (yet?) documented here. Contributors welcome!
-- You have to download the resulting Firebase service files and integrate them into your .NET MAUI csproj file. `google-services.json` is used by Android while `GoogleService-Info.plist` is accessible to iOS. Make sure the Include and the Link paths match.
```
@@ -29,10 +40,14 @@ You can use this library in any .NET MAUI project compatible to .NET 7 and highe
```
-- iOS apps need to be enabled to support push notifications. Turn on the "Push Notifications" capability of your app in the [Apple Developer Portal](https://developer.apple.com).
-#### MAUI App Startup
-This plugin provides an extension method for MauiAppBuilder `UseFirebasePushNotifications` which ensure proper startup and initialization. Call this method within your `MauiProgram` just as demonstrated in the MauiSampleApp:
+- iOS apps need to be enabled to support push notifications. Turn on the "Push Notifications" capability of your app in
+ the [Apple Developer Portal](https://developer.apple.com).
+
+### MAUI App Startup
+
+This plugin provides an extension method for MauiAppBuilder `UseFirebasePushNotifications` which ensure proper startup
+and initialization. Call this method within your `MauiProgram` just as demonstrated in the MauiSampleApp:
```csharp
var builder = MauiApp.CreateBuilder()
@@ -40,16 +55,22 @@ var builder = MauiApp.CreateBuilder()
.UseFirebasePushNotifications();
```
-`UseFirebasePushNotifications` has optional configuration parameters which are documented in another section of this document.
+`UseFirebasePushNotifications` has optional configuration parameters which are documented in another section of this
+document.
+
+### Android-specific Setup
+- Copy the google-services.json to path location Platforms\Android\Resources\google-services.json (depending on what is
+ configured in the csproj file).
+- Make sure your launcher activity (usually this is MainActivity - but not always) uses
+ `LaunchMode = LaunchMode.SingleTask`. You can also use a different LaunchMode; just be very sure what you do!
-#### Android-specific Setup
-- Copy the google-services.json to path location Platforms\Android\Resources\google-services.json (depending on what is configured in the csproj file).
-- Make sure your launcher activity (usually this is MainActivity - but not always) uses `LaunchMode = LaunchMode.SingleTask`. You can also use a different LaunchMode; just be very sure what you do!
+### iOS-specific Setup
-#### iOS-specific Setup
-- Copy the GoogleService-Info.plist to path location Platforms\iOS\GoogleService-Info.plist (depending on what is configured in the csproj file).
+- Copy the GoogleService-Info.plist to path location Platforms\iOS\GoogleService-Info.plist (depending on what is
+ configured in the csproj file).
- Extend the AppDelegate.cs file with following method exports:
+
```csharp
[Export("application:didRegisterForRemoteNotificationsWithDeviceToken:")]
[BindingImpl(BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
@@ -73,11 +94,16 @@ public void DidReceiveRemoteNotification(UIApplication application, NSDictionary
}
```
+## API Usage
-### API Usage
-`IFirebasePushNotification` is the main interface which handles most of the desired Firebase push notification features. This interface is injectable via dependency injection or accessible as a static singleton instance `IFirebasePushNotification.Current`. We strongly encourage you to use the dependency injection approach in order to keep your code testable.
+`IFirebasePushNotification` is the main interface which handles most of the desired Firebase push notification features.
+This interface is injectable via dependency injection or accessible as a static singleton instance
+`IFirebasePushNotification.Current`. We strongly encourage you to use the dependency injection approach in order to keep
+your code testable.
+
+The following lines of code demonstrate how the `IFirebasePushNotification` instance is injected in `MainViewModel` and
+assigned to a local field for later use:
-The following lines of code demonstrate how the `IFirebasePushNotification` instance is injected in `MainViewModel` and assigned to a local field for later use:
```csharp
public MainViewModel(
ILogger logger,
@@ -88,39 +114,54 @@ public MainViewModel(
}
```
-#### Managing Notification Permissions
-Before we can receive any notification we need to make sure the user has given consent to receive notifications. `INotificationPermissions` is the service you can use to check the current authorization status or ask for notification permission.
-You can either inject `INotificationPermissions` into your view models or access it via the the static singleton instance `INotificationPermissions.Current`.
+### Notification Permissions
+
+Before we can receive any notification we need to make sure the user has given consent to receive notifications.
+`INotificationPermissions` is the service you can use to check the current authorization status or ask for notification
+permission.
+You can either inject `INotificationPermissions` into your view models or access it via the the static singleton
+instance `INotificationPermissions.Current`.
- Check the current notification permission status:
+
```csharp
this.AuthorizationStatus = await this.notificationPermissions.GetAuthorizationStatusAsync();
```
- Ask the user for notification permission:
+
```csharp
await this.notificationPermissions.RequestPermissionAsync();
```
-Notification permissions are handled by the underlying operating system (iOS, Android). This library just wraps the platform-specific methods and provides a uniform API for them.
+Notification permissions are handled by the underlying operating system (iOS, Android). This library just wraps the
+platform-specific methods and provides a uniform API for them.
+
+### Register for Notifications
-#### Receive Notifications
-The main goal of a push notification client library is to receive notification messages. This library provides a set of classic .NET events to inform your code about incoming push notifications.
-Before any notification event is received, we have to inform the Firebase client library, that we're ready to receive notifications.
-`RegisterForPushNotificationsAsync` registers our app with the Firebase push notification backend and receives a token. This token is used by your own server/backend to send push notifications directly to this particular app instance.
-The token may change after some time. It is not controllable by this library if/when the token is going to be updated. The `TokenRefreshed` event will be fired whenever a new token is available.
+The main goal of a push notification client library is to receive notification messages. This library provides a set of
+classic .NET events to inform your code about incoming push notifications.
+Before any notification event is received, we have to inform the Firebase client library, that we're ready to receive
+notifications.
+`RegisterForPushNotificationsAsync` registers our app with the Firebase push notification backend and receives a token.
+This token is used by your own server/backend to send push notifications directly to this particular app instance.
+The token may change after some time. It is not controllable by this library if/when the token is going to be updated.
+The `TokenRefreshed` event will be fired whenever a new token is available.
See `Token` property and `TokenRefreshed` event provided by `IFirebasePushNotification` for more info.
```csharp
await this.firebasePushNotification.RegisterForPushNotificationsAsync();
```
-If we want to turn off any incoming notifications, we can unregister from push notifications. The `Token` can no longer be used to send push notifications to.
+If we want to turn off any incoming notifications, we can unregister from push notifications. The `Token` can no longer
+be used to send push notifications to.
+
```csharp
await this.firebasePushNotification.UnregisterForPushNotificationsAsync();
```
-Following .NET events can be subscribed:
+### Receive Notifications
+Following .NET events can be subscribed.
| Events | Description |
|------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
@@ -130,25 +171,69 @@ Following .NET events can be subscribed:
| `NotificationAction` | Is raised when the user taps a notification action. Notification actions allow users to make simple decisions when a notification is received, e.g. "Do you like to take your medicine?" could be answered with "Take medicine" and "Skip medicine". |
| `NotificationDeleted` | Is raised when the user deletes a received notification. |
-#### Topics
+#### Notification Handling Behavior
+The following table documents the behavior of each platform for incoming push notifications. We distinguish between notification message and data message.
+- **Notification messages** must have a `notification` part with `title` and/or `body`. Other sections such as `data` are optional.
+- **Data messages** must only have a `data` section (and can have platform specific options). The purpose of this message type is to send data-only messages without displaying a system notification popup.
+
+The behavior when receiving notifications is also different if the app runs in foreground or in background mode.
+The following table illustrates some use cases with different message types, priority flags and app states.
+
+| Message Type | Data Payload | OS | App State | Notification Channel | Behavior |
+|----------------------|--------------------|---------|------------|----------------------|-----------------------------------------------------------------------------|
+| Notification message | - | Android | Foreground | | `NotificationReceived` event is fired |
+| Notification message | - | iOS | Foreground | | `NotificationReceived` event is fired |
+| Notification message | `priority: "low"` | Android | Foreground | | `NotificationReceived` event is fired |
+| Notification message | `priority: "low"` | iOS | Foreground | | `NotificationReceived` event is fired |
+| Notification message | `priority: "high"` | Android | Foreground | `Importance=Default` | `NotificationReceived` event is fired + Notification icon in status bar |
+| Notification message | `priority: "high"` | Android | Foreground | `Importance=High` | `NotificationReceived` event is fired + System notification popup |
+| Notification message | `priority: "high"` | iOS | Foreground | | `NotificationReceived` event is fired + System notification popup |
+| Notification message | - | Android | Background | `Importance=Default` | Notification icon in status bar |
+| Notification message | - | Android | Background | `Importance=High` | System notification popup |
+| Notification message | - | iOS | Background | | System notification popup |
+| Notification message | `priority: "low"` | Android | Background | `Importance=Default` | Notification icon in status bar |
+| Notification message | `priority: "low"` | Android | Background | `Importance=High` | System notification popup |
+| Notification message | `priority: "low"` | iOS | Background | | System notification popup |
+| Notification message | `priority: "high"` | Android | Background | `Importance=Default` | Notification icon in status bar |
+| Notification message | `priority: "high"` | Android | Background | `Importance=High` | System notification popup |
+| Notification message | `priority: "high"` | iOS | Background | | System notification popup |
+| | | | | | |
+| Data message | | Android | Foreground | | `NotificationReceived` event is fired |
+| Data message | | iOS | Foreground | | `NotificationReceived` event is fired |
+| Data message | | Android | Background | | `NotificationReceived` event is fired as soon as app enters foreground mode |
+| Data message | | iOS | Background | | `NotificationReceived` event is fired as soon as app enters foreground mode |
+
+*) _System notification popup: Official name is **heads-up notification** on Android and **banner notification** on
+iOS._
+
+*) _Notification channels exist on Android since Android 8.0 (API level 26)._
+
+### Topics
+
The most common way of sending push notifications is by targeting notification message directly to push tokens.
Firebase allows to send push notifications to groups of devices, so-called topics.
-If a user subscribes to a topic, e.g. "weather_updates" you can send push notifications to this topic instead of a list of push tokens.
+If a user subscribes to a topic, e.g. "weather_updates" you can send push notifications to this topic instead of a list
+of push tokens.
+
+#### Subscribe to Topic
+
+Use method `SubscribeTopicAsync` with the name of the topic.
-##### Subscribe to Topics
-Use method `SubscribeTopic` with the name of the topic.
```csharp
-this.firebasePushNotification.SubscribeTopic("weather_updates");
+this.firebasePushNotification.SubscribeTopicAsync("weather_updates");
```
-Important:
-- Make sure you did run `RegisterForPushNotificationsAsync` before you subscribe to topics.
-- Topic names are case-sensitive: Registrations for topic `"weather_updates"` will not receive messages targeted to topic `"Weather_Updates"`.
+> [!IMPORTANT]
+> - Make sure you did run `RegisterForPushNotificationsAsync` before you subscribe to topics.
+> - Topic names are case-sensitive: Registrations for topic `"weather_updates"` will not receive messages targeted to topic `"Weather_Updates"`.
+
+#### Send Notifications to Topic Subscribers
-##### Send Notifications to Topic Subscribers
-Use the Firebase Admin SDK (or any other HTTP client) to send a push notification targeting subscribers of the "weather_updates" topic:
+Use the Firebase Admin SDK (or any other HTTP client) to send a push notification targeting subscribers of the topic `"weather_updates"`.
+Instead of message property `to` which addresses an FCM token directly, we use `topic` to send notification messages to a whole group of subscribed devices.
`HTTP POST https://fcm.googleapis.com/v1/projects/{{project_id}}/messages:send`
+
```
{
"message": {
@@ -169,11 +254,16 @@ Use the Firebase Admin SDK (or any other HTTP client) to send a push notificatio

-#### Notification Actions
-Notification actions are special buttons which allow for immediate response to a particular notification. A list of `NotificationActions` is consolidated within a `NotificationCategory`.
+### Notification Actions
+
+Notification actions are special buttons which allow for immediate response to a particular notification. A list of
+`NotificationActions` is consolidated within a `NotificationCategory`.
+
+#### Register Notification Actions
+
+The following example demonstrates the registration of a notification category with identifier "medication_intake" and
+two actions "Take medicine" and "Skip medicine":
-##### Register Notification Actions
-The following example demonstrates the registration of a notification category with identifier "medication_intake" and two actions "Take medicine" and "Skip medicine":
```csharp
var categories = new[]
{
@@ -186,18 +276,23 @@ var categories = new[]
```
Notification categories are usually registered at app startup time using the following method call:
+
```csharp
IFirebasePushNotification.Current.RegisterNotificationCategories(categories);
```
-##### Subscribe to Notification Actions
-Subscribe the event `IFirebasePushNotification.NotificationAction` to get notified if a user presses one of the notification action buttons.
+#### Subscribe to Notification Actions
+
+Subscribe the event `IFirebasePushNotification.NotificationAction` to get notified if a user presses one of the
+notification action buttons.
The delivered event args `FirebasePushNotificationResponseEventArgs` will let you know which action was pressed.
-##### Send Notification Actions
+#### Send Notification Actions
+
Use the Firebase Admin SDK (or any other HTTP client) to send a push notification with:
`HTTP POST https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send`
+
```
{
"message": {
@@ -229,23 +324,73 @@ If everything works fine, the mobile device with the given token displays the no

-#### More Push Notification Scenarios
+### Notification Channels
+
+Notification channels are an Android feature introduced in Android 8.0 (API level 26) that let users manage notification
+settings for different categories of notifications within the app.
+Each notification channel represents a distinct type of notification (such as chat messages, medication intake, or
+promotions) and allows users to customize notification preferences per channel, rather than for the whole app.
+
+#### Default Notification Channel
+
+Your app must always have at least one notification channel. This library will use this channel for any notifications
+that are not targeting a specific channel. This ensures that push notifications are delivered even if a custom channel
+is not set up.
+
+It is highly recommended to create the default notification channel by yourself so that all properties are under your
+control.
+Use `INotificationChannels.Channels` methods to create notification channels manually at startup or specify them in the
+Android-specific options under `UseFirebasePushNotifications(o => o.Android.NotificationChannels = ...)`).
+To get an idea of how to use the `NotificationChannels` option, take a look at `MauiProgram.cs` in the sample app in
+this repository.
+
+#### Notification Channel Importance
+
+When your app (or this library) creates a notification channel, you must specify its importance (e.g., `Low`, `Default`,
+`High`).
+Importance controls how notifications are presented (such as whether they make a sound or appear as a heads-up
+notification).
+
+> [!IMPORTANT]
+> The importance level can only be set once when the channel is created. It cannot be changed afterward.
+> If you need to modify a channel’s importance, you must create a new channel with a different ID.
+
+#### Notification Channel Groups
+
+Multiple notification channels may be grouped together within a notification channel group.
+Use `INotificationChannels.ChannelGroups` methods to create/delete notification channel groups.
+
+### More Push Notification Scenarios
+
There are a lot of features in this library that can be controlled via specific data flags. The most common scenarios
-are end-to-end tested using postman calls. You can find an up-to-date postman collection in this repository:
-[FCM Plugin.FirebasePushNotifications.postman_collection.json]()
+are end-to-end tested with the MauiSampleApp using postman calls. You can find an
+up-to-date [postman collection]() in this repository.
- Import the collection in postman.
- Adjust the variables, especially the `project_id` and the `fcm_token` accordingly.
-- Get a Bearer authentication token either by selecting the Auth Type "Firebase Cloud Messaging API (Oauth 2.0)" or by creating it manually via https://developers.google.com/oauthplayground (see this [youtube video](https://www.youtube.com/watch?v=PYfpBwupoMQ)).
+- Get a Bearer authentication token either by selecting the Auth Type "Firebase Cloud Messaging API (Oauth 2.0)" or by
+ creating it manually via https://developers.google.com/oauthplayground (see
+ this [youtube video](https://www.youtube.com/watch?v=PYfpBwupoMQ)).
### Options
+
> *to be documented*
-### Contribution
-Your contribution is valuable! If you find a bug or want to propose a new feature, feel free to create a new issue [here](https://github.com/thomasgalliker/Plugin.FirebasePushNotifications/issues/new/choose).
+## Contribution
+
+If you find a bug or want to propose a new feature, feel free to create a new
+issue [here](https://github.com/thomasgalliker/Plugin.FirebasePushNotifications/issues/new/choose).
Please use the **predefined issue templates** when submitting a new issue.
-### Links
+## Thank You
+
+Your contribution is valuable!
+Open source software isn’t just something you can pick up for free — it represents the hard work and dedication of many
+people who often not even know each other.
+We sincerely appreciate the time, effort, and dedication shown by everyone who helps keep this plugin going forward.
+
+## Links
+
- FCM messages, data format, concepts and options:
https://firebase.google.com/docs/cloud-messaging/concept-options
diff --git a/ReleaseNotes.txt b/ReleaseNotes.txt
new file mode 100644
index 00000000..37fa82f8
--- /dev/null
+++ b/ReleaseNotes.txt
@@ -0,0 +1,48 @@
+4.0
+- Improved notification channel handling during app startup.
+- Data-only notifications are no longer displayed in the notification tray.
+- NotificationBuilder support for Android API 26 and below (where no notification channels are available).
+- OpenNotificationSettings now also works for Android API 26 and below.
+- Synchronized notification handling behavior between Android and iOS.
+- Renamed topic methods to follow the Async pattern: SubscribeToTopicAsync, UnsubscribeFromTopicAsync, etc.
+- Use AddOnCompleteListener for asynchronous tasks in Android.
+- Remove support for net7.0 and net8.0.
+- Add support for net9.0 and net10.0.
+- Bug fixes and refactorings.
+
+3.2
+- Improved default notification channel handling.
+- Bug fixes and refactorings.
+
+3.1
+- Extend INotificationChannels to manage notification channel groups.
+- Internal refactoring of INotificationChannels implementation.
+- Removed properties IsActive and IsDefault from NotificationChannelRequest. Set the default notification channel via UseFirebasePushNotifications(o => o.Android.DefaultNotificationChannelId = ...).
+- Configure initial list of notification channels via o.Android.NotificationChannels and notification groups via o.Android.NotificationChannelGroups.
+
+3.0
+- Update firebase-ios-sdk by replacing nuget package Xamarin.Firebase.iOS.CloudMessaging with AdamE.Firebase.iOS.CloudMessaging.
+
+2.5
+- Move static properties from Android's FirebasePushNotificationManager to FirebasePushNotificationAndroidOptions.
+- iOS 18 workaround for duplicate notifications in foreground mode.
+- iOS options to override default UNNotificationPresentationOptions for notifications received in foreground mode.
+- Handle gcm.notification.click_action payload as click_action in Android.
+
+2.4
+- Refactor instanciation of IFirebasePushNotification.
+- Refactor startup procedure of platform-specific services.
+- Add singleton instance INotificationPermissions.Current.
+
+2.3
+- General bug fixes and code cleanup.
+- Bug fixes in the area of topic subscriptions.
+- IFirebasePushNotification.Current.
+- Add singleton instance IFirebasePushNotification.Current and INotificationPermissions.Current.
+
+2.2
+- Complete refactoring of the original 1.x implementation.
+- Simplified APIs, less static code, support for dependency injection.
+
+1.0
+- Initial release.
\ No newline at end of file
diff --git a/Samples/MauiSampleApp/App.xaml.cs b/Samples/MauiSampleApp/App.xaml.cs
index 7662a911..bf23227f 100644
--- a/Samples/MauiSampleApp/App.xaml.cs
+++ b/Samples/MauiSampleApp/App.xaml.cs
@@ -1,24 +1,31 @@
-using MauiSampleApp.ViewModels;
+using MauiSampleApp.Services;
+using MauiSampleApp.ViewModels;
using MauiSampleApp.Views;
namespace MauiSampleApp
{
public partial class App : Application
{
+ private readonly IServiceProvider serviceProvider;
+
public App(IServiceProvider serviceProvider)
{
+ this.serviceProvider = serviceProvider;
this.InitializeComponent();
+ }
- var mainPage = serviceProvider.GetRequiredService();
- this.MainPage = new NavigationPage(mainPage);
+ protected override Window CreateWindow(IActivationState activationState)
+ {
+ var mainPage = this.serviceProvider.GetRequiredService();
+ return new Window(new NavigationPage(mainPage));
}
protected override void OnResume()
{
- if (this.MainPage is NavigationPage { CurrentPage: MainPage { BindingContext: MainViewModel mainViewModel } })
+ if (this.GetCurrentPage() is MainPage { BindingContext: MainViewModel mainViewModel })
{
mainViewModel.OnResume();
}
}
}
-}
\ No newline at end of file
+}
diff --git a/Samples/MauiSampleApp/MauiProgram.cs b/Samples/MauiSampleApp/MauiProgram.cs
index 185adc9a..289532e0 100644
--- a/Samples/MauiSampleApp/MauiProgram.cs
+++ b/Samples/MauiSampleApp/MauiProgram.cs
@@ -7,6 +7,7 @@
using Plugin.FirebasePushNotifications.Model.Queues;
using MauiSampleApp.Services.Logging;
using NLog.Extensions.Logging;
+using Superdev.Maui;
#if ANDROID
using Android.App;
@@ -66,6 +67,7 @@ public static MauiApp CreateMauiApp()
// o.iOS.iOS18Workaround.Enable = true;
#endif
})
+ .UseSuperdevMaui()
.ConfigureFonts(fonts =>
{
fonts.AddFont("IBMPlexSans-Regular.ttf", "IBMPlexSans");
diff --git a/Samples/MauiSampleApp/MauiSampleApp.csproj b/Samples/MauiSampleApp/MauiSampleApp.csproj
index 4063ce16..5a00e097 100644
--- a/Samples/MauiSampleApp/MauiSampleApp.csproj
+++ b/Samples/MauiSampleApp/MauiSampleApp.csproj
@@ -1,11 +1,11 @@
- net8.0;net8.0-android;net8.0-ios
+ net10.0-android36.0;net10.0-ios26.2
Exe
MauiSampleApp
true
- 8.0.100
+ 10.0.51
true
enable
disable
@@ -15,22 +15,17 @@
Firebase Push Demo
- com.companyname.firebasepushdemo
+ ch.superdev.firebasepushdemo
da8d1bd2-3ced-4171-b0da-3f1a7806e5af
- 1.0
+ 1.0.0
1
- 12.0
+ 12.2
24.0
-
-
- Library
-
-
None
apk
false
- true
+ false
SdkOnly
@@ -134,13 +129,13 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/Samples/MauiSampleApp/Platforms/Android/AndroidManifest.xml b/Samples/MauiSampleApp/Platforms/Android/AndroidManifest.xml
index 9970642f..9ce82b1a 100644
--- a/Samples/MauiSampleApp/Platforms/Android/AndroidManifest.xml
+++ b/Samples/MauiSampleApp/Platforms/Android/AndroidManifest.xml
@@ -24,10 +24,6 @@
-
-
-
-
diff --git a/Samples/MauiSampleApp/Platforms/Android/Notifications/NotificationChannelSamples.cs b/Samples/MauiSampleApp/Platforms/Android/Notifications/NotificationChannelSamples.cs
index b784bd45..44eaeb64 100644
--- a/Samples/MauiSampleApp/Platforms/Android/Notifications/NotificationChannelSamples.cs
+++ b/Samples/MauiSampleApp/Platforms/Android/Notifications/NotificationChannelSamples.cs
@@ -23,26 +23,44 @@ public static IEnumerable GetAll()
{
ChannelId = "test_channel_1",
ChannelName = "Test Channel 1",
- Description = "Description for test channel 1",
+ Description = "Low priority test channel",
LockscreenVisibility = NotificationVisibility.Public,
- Importance = NotificationImportance.High,
+ Importance = NotificationImportance.Low,
};
yield return new NotificationChannelRequest
{
ChannelId = "test_channel_2",
ChannelName = "Test Channel 2",
- Description = "Description for test channel 2",
- Group = NotificationChannelGroupSamples.TestGroup1.GroupId,
+ Description = "Default priority test channel",
LockscreenVisibility = NotificationVisibility.Public,
- Importance = NotificationImportance.High,
+ Importance = NotificationImportance.Default,
};
yield return new NotificationChannelRequest
{
ChannelId = "test_channel_3",
ChannelName = "Test Channel 3",
- Description = "Description for test channel 3",
+ Description = "High priority test channel",
+ LockscreenVisibility = NotificationVisibility.Public,
+ Importance = NotificationImportance.High,
+ };
+
+ yield return new NotificationChannelRequest
+ {
+ ChannelId = "test_channel_4",
+ ChannelName = "Test Channel 4",
+ Description = "Test channel assigned to group 1",
+ Group = NotificationChannelGroupSamples.TestGroup1.GroupId,
+ LockscreenVisibility = NotificationVisibility.Public,
+ Importance = NotificationImportance.High,
+ };
+
+ yield return new NotificationChannelRequest
+ {
+ ChannelId = "test_channel_5",
+ ChannelName = "Test Channel 5",
+ Description = "Test channel assigned to group 1",
Group = NotificationChannelGroupSamples.TestGroup1.GroupId,
LockscreenVisibility = NotificationVisibility.Public,
Importance = NotificationImportance.High,
diff --git a/Samples/MauiSampleApp/Platforms/Android/Resources/drawable/ic_radiobox_checked.png b/Samples/MauiSampleApp/Platforms/Android/Resources/drawable/ic_radiobox_checked.png
new file mode 100644
index 00000000..1192c5a2
Binary files /dev/null and b/Samples/MauiSampleApp/Platforms/Android/Resources/drawable/ic_radiobox_checked.png differ
diff --git a/Samples/MauiSampleApp/Platforms/Android/Resources/drawable/ic_radiobox_unchecked.png b/Samples/MauiSampleApp/Platforms/Android/Resources/drawable/ic_radiobox_unchecked.png
new file mode 100644
index 00000000..d8ee7956
Binary files /dev/null and b/Samples/MauiSampleApp/Platforms/Android/Resources/drawable/ic_radiobox_unchecked.png differ
diff --git a/Samples/MauiSampleApp/Platforms/Android/Resources/values/colors.xml b/Samples/MauiSampleApp/Platforms/Android/Resources/values/colors.xml
index d5df0927..8e7a6bb1 100644
--- a/Samples/MauiSampleApp/Platforms/Android/Resources/values/colors.xml
+++ b/Samples/MauiSampleApp/Platforms/Android/Resources/values/colors.xml
@@ -1,7 +1,7 @@
#FDA612
- #F6820C
- #F6820C
+ #FDA612
+ #FDA612
#FF00FF
\ No newline at end of file
diff --git a/Samples/MauiSampleApp/Services/ApplicationExtensions.cs b/Samples/MauiSampleApp/Services/ApplicationExtensions.cs
new file mode 100644
index 00000000..538452d3
--- /dev/null
+++ b/Samples/MauiSampleApp/Services/ApplicationExtensions.cs
@@ -0,0 +1,17 @@
+namespace MauiSampleApp.Services
+{
+ internal static class ApplicationExtensions
+ {
+ public static Page GetRootPage(this Application application)
+ {
+ return application.Windows.FirstOrDefault()?.Page
+ ?? throw new InvalidOperationException("The application does not have an active window page.");
+ }
+
+ public static Page GetCurrentPage(this Application application)
+ {
+ var page = application.GetRootPage();
+ return page is NavigationPage navigationPage ? navigationPage.CurrentPage : page;
+ }
+ }
+}
diff --git a/Samples/MauiSampleApp/Services/DialogService.cs b/Samples/MauiSampleApp/Services/DialogService.cs
index 3feb6122..03fa586a 100644
--- a/Samples/MauiSampleApp/Services/DialogService.cs
+++ b/Samples/MauiSampleApp/Services/DialogService.cs
@@ -6,8 +6,9 @@ public Task ShowDialogAsync(string title, string message, string cancel)
{
return MainThread.InvokeOnMainThreadAsync(async () =>
{
- await Application.Current.MainPage.DisplayAlert(title, message, cancel);
+ var application = Application.Current ?? throw new InvalidOperationException("Application.Current is not available.");
+ await application.GetCurrentPage().DisplayAlertAsync(title, message, cancel);
});
}
}
-}
\ No newline at end of file
+}
diff --git a/Samples/MauiSampleApp/Services/MauiNavigationService.cs b/Samples/MauiSampleApp/Services/MauiNavigationService.cs
index c50d29d5..01c4ea2e 100644
--- a/Samples/MauiSampleApp/Services/MauiNavigationService.cs
+++ b/Samples/MauiSampleApp/Services/MauiNavigationService.cs
@@ -12,12 +12,14 @@ public MauiNavigationService(IServiceProvider serviceProvider)
public async Task PushAsync() where TPage : Page
{
var page = this.serviceProvider.GetRequiredService();
- await Application.Current.MainPage.Navigation.PushAsync(page);
+ var application = Application.Current ?? throw new InvalidOperationException("Application.Current is not available.");
+ await application.GetRootPage().Navigation.PushAsync(page);
}
public async Task PopAsync()
{
- await Application.Current.MainPage.Navigation.PopAsync();
+ var application = Application.Current ?? throw new InvalidOperationException("Application.Current is not available.");
+ await application.GetRootPage().Navigation.PopAsync();
}
}
-}
\ No newline at end of file
+}
diff --git a/Samples/MauiSampleApp/ViewModels/MainViewModel.cs b/Samples/MauiSampleApp/ViewModels/MainViewModel.cs
index 73a65e31..a5263c4a 100644
--- a/Samples/MauiSampleApp/ViewModels/MainViewModel.cs
+++ b/Samples/MauiSampleApp/ViewModels/MainViewModel.cs
@@ -546,7 +546,10 @@ private async Task OpenNotificationChannelSettingsAsync()
{
#if ANDROID
var defaultNotificationChannel = this.notificationChannels.Channels.GetDefault();
- this.notificationChannels.OpenNotificationChannelSettings(defaultNotificationChannel.Id);
+ if (defaultNotificationChannel != null)
+ {
+ this.notificationChannels.OpenNotificationChannelSettings(defaultNotificationChannel.Id);
+ }
#endif
}
catch (Exception ex)
@@ -674,7 +677,12 @@ void DeleteNotificationChannel(string id)
this.UpdateNotificationChannels();
}
- return new NotificationChannelViewModel(notificationChannelViewModelLogger, this.dialogService, DeleteNotificationChannel, c);
+ return new NotificationChannelViewModel(
+ notificationChannelViewModelLogger,
+ this.dialogService,
+ this.notificationChannels,
+ DeleteNotificationChannel,
+ c);
})
.ToArray();
@@ -738,7 +746,7 @@ private async Task SubscribeToTopicAsync()
try
{
var topic = this.Topic;
- this.firebasePushNotification.SubscribeTopic(topic);
+ await this.firebasePushNotification.SubscribeTopicAsync(topic);
this.UpdateSubscribedTopics();
this.Topic = null;
}
@@ -753,7 +761,7 @@ private async Task UnsubscribeFromTopicAsync(string topic)
{
try
{
- this.firebasePushNotification.UnsubscribeTopic(topic);
+ await this.firebasePushNotification.UnsubscribeTopicAsync(topic);
this.UpdateSubscribedTopics();
}
catch (Exception ex)
@@ -772,7 +780,7 @@ private async Task UnsubscribeAllTopicsAsync()
{
try
{
- this.firebasePushNotification.UnsubscribeAllTopics();
+ await this.firebasePushNotification.UnsubscribeAllTopicsAsync();
this.UpdateSubscribedTopics();
}
catch (Exception ex)
@@ -930,9 +938,10 @@ private async Task OpenUrlAsync(string url)
}
}
- public void OnResume()
+ public async void OnResume()
{
- _ = this.UpdateAuthorizationStatusAsync();
+ await this.UpdateAuthorizationStatusAsync();
+ await this.GetNotificationChannelsAsync();
}
}
}
\ No newline at end of file
diff --git a/Samples/MauiSampleApp/ViewModels/NotificationCategorySamples.cs b/Samples/MauiSampleApp/ViewModels/NotificationCategorySamples.cs
index 5ba0356b..47a01587 100644
--- a/Samples/MauiSampleApp/ViewModels/NotificationCategorySamples.cs
+++ b/Samples/MauiSampleApp/ViewModels/NotificationCategorySamples.cs
@@ -23,16 +23,16 @@ public static IEnumerable GetAll()
});
yield return new NotificationCategory("contract", new[]
{
- new NotificationAction("Accept", "Accept", NotificationActionType.Default, "accept"),
- new NotificationAction("Reject", "Reject", NotificationActionType.Default, "reject")
+ new NotificationAction("Accept", "Accept", NotificationActionType.Default, "ic_radiobox_checked"),
+ new NotificationAction("Reject", "Reject", NotificationActionType.Default, "ic_radiobox_unchecked")
});
yield return new NotificationCategory("dismiss",new []
{
- new NotificationAction("dismiss","Dismiss", NotificationActionType.Default),
+ new NotificationAction("dismiss","Dismiss", NotificationActionType.Destructive),
});
yield return new NotificationCategory("navigate", new []
{
- new NotificationAction("dismiss", "Dismiss", NotificationActionType.Default),
+ new NotificationAction("dismiss", "Dismiss", NotificationActionType.Destructive),
new NotificationAction("navigate", "Navigate To", NotificationActionType.Foreground)
});
}
diff --git a/Samples/MauiSampleApp/ViewModels/NotificationChannelViewModel.cs b/Samples/MauiSampleApp/ViewModels/NotificationChannelViewModel.cs
index 043fc026..2f06ed1a 100644
--- a/Samples/MauiSampleApp/ViewModels/NotificationChannelViewModel.cs
+++ b/Samples/MauiSampleApp/ViewModels/NotificationChannelViewModel.cs
@@ -29,15 +29,17 @@ public class NotificationChannelViewModel
public NotificationChannelViewModel(
ILogger logger,
IDialogService dialogService,
+ INotificationChannels notificationChannels,
Action deleteNotificationChannel,
NotificationChannel notificationChannel)
{
this.ChannelId = notificationChannel.Id;
this.ChannelName = notificationChannel.Name;
+ this.IsDefault = notificationChannel.Id == notificationChannels.Channels.DefaultNotificationChannelId;
this.Description = notificationChannel.Description;
- this.LockscreenVisibility = Enum.GetName(notificationChannel.LockscreenVisibility) ?? $"{notificationChannel.LockscreenVisibility}";
+ this.LockscreenVisibility = Enum.GetName(typeof(NotificationVisibility), notificationChannel.LockscreenVisibility) ?? $"{notificationChannel.LockscreenVisibility}";
this.Group = notificationChannel.Group ?? "null";
- this.Importance = Enum.GetName(notificationChannel.Importance);
+ this.Importance = Enum.GetName(typeof(NotificationImportance), notificationChannel.Importance);
this.logger = logger;
this.dialogService = dialogService;
@@ -49,6 +51,8 @@ public NotificationChannelViewModel(
public string ChannelName { get; }
+ public bool IsDefault { get; }
+
public string Description { get; }
public string LockscreenVisibility { get; }
diff --git a/Samples/MauiSampleApp/Views/ItemTemplates/NotificationChannelItemTemplate.xaml b/Samples/MauiSampleApp/Views/ItemTemplates/NotificationChannelItemTemplate.xaml
index 587e247a..4f77a659 100644
--- a/Samples/MauiSampleApp/Views/ItemTemplates/NotificationChannelItemTemplate.xaml
+++ b/Samples/MauiSampleApp/Views/ItemTemplates/NotificationChannelItemTemplate.xaml
@@ -7,9 +7,10 @@
x:DataType="vm:NotificationChannelViewModel">
+ RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto">
+ Text="IsDefault:" />
+
+
+