About Push Notifications
Implement Actionable Notifications
Configure WebSockets
REST Wrappers for SFAP APIs
Using Key-Value Stores for Secure Data Storage
Dark Mode and Dark Theme Settings
Mobile SDK apps can use the Salesforce Notifications API to implement actionable notifications: push notifications that include interactive action buttons the user can tap directly from the notification tray, without opening the app.
Before displaying actionable notifications, retrieve the notification types defined for your org. Each notification type describes the notification’s appearance and its list of available actions.
The Notifications API endpoint is GET /{apiVersion}/connect/notifications/types.
Use PushNotificationManager to fetch and cache notification types.
1import SalesforceSDKCore
2
3Task {
4 do {
5 try await PushNotificationManager.sharedInstance().fetchAndStoreNotificationTypes()
6 print("Notification types fetched and cached")
7 } catch {
8 print("Failed to fetch notification types: \(error)")
9 }
10}The fetchAndStoreNotificationTypes() method:
UNUserNotificationCenter.For custom handling, use the low-level API instead.
1import SalesforceSDKCore
2
3let request = RestClient.shared.request(forNotificationTypesWithVersion: RestClient.shared.apiVersion)
4do {
5 let response = try await RestClient.shared.send(request: request)
6 let types = try response.asDecodable(type: [NotificationType].self)
7 // Process types manually.
8} catch {
9 print("Failed to fetch: \(error)")
10}Use SalesforceSDKManager to fetch notification types.
1import com.salesforce.androidsdk.app.SalesforceSDKManager
2import com.salesforce.androidsdk.rest.NotificationsApiClient
3
4val sdkManager = SalesforceSDKManager.getInstance()
5val restClient = sdkManager.clientManager.peekRestClient(sdkManager.userAccountManager.currentUser)
6val apiClient = NotificationsApiClient(restClient)
7
8try {
9 val notificationTypes = apiClient.fetchNotificationsTypes()
10 notificationTypes?.notificationTypes?.forEach { type ->
11 Log.d(TAG, "Type: ${type.label}, API Name: ${type.apiName}")
12 }
13} catch (e: Exception) {
14 Log.e(TAG, "Failed to fetch notification types", e)
15}Register Android notification channels
After fetching notification types, register them as Android notification channels. Typically, call this from your Application.onCreate() method, after Mobile SDK initialization.
1import com.salesforce.androidsdk.push.PushService
2
3val notificationTypes = apiClient.fetchNotificationsTypes()
4if (notificationTypes != null) {
5 PushService.registerNotificationChannels(notificationTypes)
6}This code creates a notification channel for each type and groups the channels under “Salesforce Notifications”.
See also: Notifications Resources.
Register the notification categories (iOS) or channels with action buttons (Android) using the action data that you retrieved.
Mobile SDK automatically registers notification categories when you call fetchAndStoreNotificationTypes(). Behind the scenes, it uses NotificationCategoryFactory to convert notification types into UNNotificationCategory and UNNotificationAction objects.
1// Called once at app launch or when notification types change.
2try await PushNotificationManager.sharedInstance().fetchAndStoreNotificationTypes()
3// Categories are now registered with UNUserNotificationCenter.If you want to filter which notification types are registered—for example, only certain types for certain users—set a filter on UserAccountManager.
1import SalesforceSDKCore
2
3UserAccountManager.shared.filterSupportedNotificationTypes = { types in
4 // Return only the types that you want to support in this app.
5 return types.filter { $0.apiName.hasPrefix("MyApp_") }
6}
7
8// Then fetch as normal.
9try await PushNotificationManager.sharedInstance().fetchAndStoreNotificationTypes()For reference, here’s how Mobile SDK registers categories with iOS under the hood.
1import UserNotifications
2
3let approveAction = UNNotificationAction(
4 identifier: "APPROVE_ACTION",
5 title: "Approve",
6 options: [.authenticationRequired]
7)
8let rejectAction = UNNotificationAction(
9 identifier: "REJECT_ACTION",
10 title: "Reject",
11 options: [.destructive]
12)
13let category = UNNotificationCategory(
14 identifier: "APPROVAL_REQUEST",
15 actions: [approveAction, rejectAction],
16 intentIdentifiers: []
17)
18UNUserNotificationCenter.current().setNotificationCategories([category])Mobile SDK automatically registers a notification channel for each notification type after push registration succeeds. When you build a notification, use the notification type’s type value as the channel ID. Add action buttons dynamically when you build the notification in your PushNotificationReceiver or PushNotificationsAdapter.
1import android.app.PendingIntent.FLAG_IMMUTABLE
2import android.app.PendingIntent.getBroadcast
3import android.content.Intent
4import android.os.Bundle
5import androidx.core.app.NotificationCompat
6import com.salesforce.androidsdk.R.drawable.sf__salesforce_logo
7import java.util.UUID.randomUUID
8
9// The channel ID is the Salesforce notification type's `type` value. The SDK
10// registers a channel per notification type automatically after push
11// registration succeeds, so reuse that ID here.
12NotificationCompat.Builder(context, actionableNotificationsType.type ?: return).apply {
13 setContentTitle(alertTitle)
14 setContentText(alert)
15 setSmallIcon(sf__salesforce_logo)
16 // Add action buttons from the action group that matches the notification's group.
17 actionableNotificationsType.actionGroups
18 ?.firstOrNull { it.name == actionableNotificationContent.act?.group }
19 ?.actions?.forEach { action ->
20 val intent = Intent(BROADCAST_INTENT_ACTION_INVOKE_SALESFORCE_NOTIFICATION_ACTION).apply {
21 // A unique identifier keeps each action's PendingIntent distinct;
22 // extras alone are NOT part of PendingIntent equality.
23 identifier = randomUUID().toString()
24 putExtras(Bundle().apply {
25 putString(NOTIFICATION_EXTRAS_KEY_SALESFORCE_ACTIONABLE_NOTIFICATION_ID, actionableNotificationContent.nid)
26 putString(NOTIFICATION_EXTRAS_KEY_SALESFORCE_ACTIONABLE_NOTIFICATION_ACTION_KEY, action.actionKey)
27 })
28 }
29 addAction(
30 sf__salesforce_logo,
31 action.label ?: return@forEach,
32 getBroadcast(context, 0, intent, FLAG_IMMUTABLE)
33 )
34 }
35}.build()The Mobile SDK template app includes a full example in PushNotificationsAdapter.kt.
When the user taps a notification action, handle it in your app delegate (iOS) or broadcast receiver (Android).
Implement UNUserNotificationCenterDelegate in your AppDelegate.
1import UserNotifications
2import SalesforceSDKCore
3
4extension AppDelegate: UNUserNotificationCenterDelegate {
5
6 func userNotificationCenter(_ center: UNUserNotificationCenter,
7 willPresent notification: UNNotification,
8 withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
9 completionHandler([.banner, .sound, .badge])
10 }
11
12 func userNotificationCenter(_ center: UNUserNotificationCenter,
13 didReceive response: UNNotificationResponse,
14 withCompletionHandler completionHandler: @escaping () -> Void) {
15 let actionIdentifier = response.actionIdentifier
16 let userInfo = response.notification.request.content.userInfo
17
18 // Extract the notification ID from the payload.
19 let sfdc = userInfo["sfdc"] as? [String: Any]
20 guard actionIdentifier != UNNotificationDefaultActionIdentifier,
21 actionIdentifier != UNNotificationDismissActionIdentifier,
22 let nid = sfdc?["nid"] as? String else {
23 completionHandler()
24 return
25 }
26
27 // Invoke the server-side action.
28 Task {
29 do {
30 let result = try await PushNotificationManager.sharedInstance().invokeServerNotificationAction(
31 notificationId: nid,
32 actionIdentifier: actionIdentifier
33 )
34 print("Action invoked: \(result.message)")
35 } catch {
36 print("Failed to invoke notification action: \(error)")
37 }
38 completionHandler()
39 }
40 }
41}Set the delegate in application(_:didFinishLaunchingWithOptions:).
1UNUserNotificationCenter.current().delegate = selfRegister a BroadcastReceiver to handle notification actions.
1import android.content.BroadcastReceiver
2import android.content.Context
3import android.content.Intent
4import android.util.Log
5import com.salesforce.androidnativekotlintemplate.PushNotificationsAdapter.Companion.NOTIFICATION_EXTRAS_KEY_SALESFORCE_ACTIONABLE_NOTIFICATION_ACTION_KEY
6import com.salesforce.androidnativekotlintemplate.PushNotificationsAdapter.Companion.NOTIFICATION_EXTRAS_KEY_SALESFORCE_ACTIONABLE_NOTIFICATION_ID
7import com.salesforce.androidsdk.app.SalesforceSDKManager
8import kotlinx.coroutines.CoroutineScope
9import kotlinx.coroutines.Dispatchers.Default
10import kotlinx.coroutines.launch
11
12class InvokeNotificationActionBroadcastIntentReceiver : BroadcastReceiver() {
13 override fun onReceive(context: Context, intent: Intent) {
14 // invokeServerNotificationAction is a blocking network call (sendSync),
15 // so run it off the main thread rather than in onReceive directly.
16 CoroutineScope(Default).launch {
17 try {
18 val notificationId = intent.extras?.getString(
19 NOTIFICATION_EXTRAS_KEY_SALESFORCE_ACTIONABLE_NOTIFICATION_ID
20 ) ?: return@launch
21 val actionKey = intent.extras?.getString(
22 NOTIFICATION_EXTRAS_KEY_SALESFORCE_ACTIONABLE_NOTIFICATION_ACTION_KEY
23 ) ?: return@launch
24 val response = SalesforceSDKManager.getInstance().invokeServerNotificationAction(
25 notificationId = notificationId,
26 actionKey = actionKey
27 )
28 response?.message?.let {
29 Log.i(TAG, "Action invoked: $it")
30 }
31 } catch (e: Exception) {
32 Log.e(TAG, "Failed to invoke notification action", e)
33 }
34 }
35 }
36
37 companion object {
38 private const val TAG = "InvokeNotificationAction"
39 }
40}Register the receiver in your Application.onCreate().
1import androidx.core.content.ContextCompat
2import android.content.IntentFilter
3import com.salesforce.androidnativekotlintemplate.MainApplication.Companion.BROADCAST_INTENT_ACTION_INVOKE_SALESFORCE_NOTIFICATION_ACTION
4
5ContextCompat.registerReceiver(
6 this, // Application context.
7 InvokeNotificationActionBroadcastIntentReceiver(),
8 IntentFilter(BROADCAST_INTENT_ACTION_INVOKE_SALESFORCE_NOTIFICATION_ACTION),
9 ContextCompat.RECEIVER_NOT_EXPORTED
10)The Notifications API endpoint is POST /{apiVersion}/connect/notifications/{notificationId}/actions/{actionKey}.
Use PushNotificationManager.invokeServerNotificationAction().
1import SalesforceSDKCore
2
3Task {
4 do {
5 let result = try await PushNotificationManager.sharedInstance().invokeServerNotificationAction(
6 notificationId: "0DtRM000000001234",
7 actionIdentifier: "approve"
8 )
9 print("Server response: \(result.message)")
10 } catch {
11 print("Failed to invoke action: \(error)")
12 }
13}For multi-user apps, use a custom RestClient.
1let result = try await PushNotificationManager.sharedInstance().invokeServerNotificationAction(
2 client: customRestClient,
3 notificationId: notificationId,
4 actionIdentifier: actionIdentifier
5)Use SalesforceSDKManager.invokeServerNotificationAction().
1import com.salesforce.androidsdk.app.SalesforceSDKManager
2
3val response = SalesforceSDKManager.getInstance().invokeServerNotificationAction(
4 notificationId = "0DtRM000000001234",
5 actionKey = "approve"
6)
7
8response?.message?.let {
9 Log.i(TAG, "Server response: $it")
10}For multi-user apps, use a custom RestClient.
1import com.salesforce.androidsdk.rest.NotificationsApiClient
2
3val user = sdkManager.userAccountManager.currentUser
4val customRestClient = sdkManager.clientManager.peekRestClient(user)
5val apiClient = NotificationsApiClient(customRestClient)
6val response = apiClient.submitNotificationAction(
7 notificationId = notificationId,
8 actionKey = actionKey
9)Client-side actions run entirely in the app and don’t require a server call. Implement them in the standard platform notification delegate methods.
Check the action identifier in userNotificationCenter(_:didReceive:withCompletionHandler:) and handle client-side actions before calling the server API.
1func userNotificationCenter(_ center: UNUserNotificationCenter,
2 didReceive response: UNNotificationResponse,
3 withCompletionHandler completionHandler: @escaping () -> Void) {
4 let actionIdentifier = response.actionIdentifier
5
6 // Handle client-side actions.
7 if actionIdentifier == "dismiss_local" {
8 print("User dismissed the notification locally")
9 completionHandler()
10 return
11 }
12
13 // For server-side actions, invoke the API. See "Invoke Server-Side Notification Actions".
14}In your BroadcastReceiver, check the action type and handle client-side actions.
1override fun onReceive(context: Context, intent: Intent) {
2 val actionKey = intent.extras?.getString("actionKey") ?: return
3
4 // Check if this is a client-side action.
5 if (actionKey == "dismiss_local") {
6 Log.i(TAG, "User dismissed the notification locally")
7 return
8 }
9
10 // For server-side actions, invoke the API. See "Invoke Server-Side Notification Actions".
11 CoroutineScope(Default).launch {
12 // ...
13 }
14}These models represent the notification types and actions that the Notifications API returns.
1public class NotificationType: NSObject, Codable {
2 public let type: String
3 public let apiName: String
4 public let label: String
5 public let actionGroups: [ActionGroup]?
6}
7
8public class ActionGroup: NSObject, Codable {
9 public let name: String
10 public let actions: [Action]
11}
12
13public class Action: NSObject, Codable {
14 public let name: String
15 public let identifier: String // Maps from the JSON key "actionKey".
16 public let label: String
17 public let type: String // "NotificationApiAction" or "ClientAction".
18}
19
20public class ActionResultRepresentation: NSObject, Codable {
21 public let message: String
22}1@Serializable
2data class NotificationsTypesResponseBody(
3 val notificationTypes: Array<NotificationType>? = null
4) {
5 @Serializable
6 data class NotificationType(
7 val actionGroups: Array<ActionGroup>? = null,
8 val apiName: String? = null,
9 val label: String? = null,
10 val type: String? = null
11 ) {
12 @Serializable
13 data class ActionGroup(
14 val name: String? = null,
15 val actions: Array<Action>? = null
16 ) {
17 @Serializable
18 data class Action(
19 val actionKey: String? = null,
20 val label: String? = null,
21 val name: String? = null,
22 val type: String? = null
23 )
24 }
25 }
26}
27
28@Serializable
29data class NotificationsActionsResponseBody(
30 val message: String? = null
31)Mobile SDK 14.0 includes complete sample apps that demonstrate actionable notifications end to end.
PushNotificationsAdapter.kt, InvokeNotificationActionBroadcastIntentReceiver, and notification channel registration.AppDelegate implementing UNUserNotificationCenterDelegate and server action invocation.We've Moved