Mobile Service Extensions
Using the iOS and Android Notification Service Extension in your mobile apps.
Android Notification Service Extension
Add the below Notification Extension Code if you want to do one of the following:
- Receive data in the background with or without displaying a notification.
- Override specific notification settings depending on client side app logic such as custom accent color, vibration pattern, or other any other
NotificationCompat
options available. See Android's documentation on the NotificationCompat options.
1. Create a class for the Service Extension
Create a class that extends INotificationServiceExtension
and implement the onNotificationReceived
method.
The method onNotificationReceived
parameter is event
of type INotificationReceivedEvent .
package your.package.name
import com.onesignal.notifications.IActionButton;
import com.onesignal.notifications.IDisplayableMutableNotification;
import com.onesignal.notifications.INotificationReceivedEvent;
import com.onesignal.notifications.INotificationServiceExtension;
@Keep // Keep is required to prevent minification from renaming or removing your class
public class NotificationServiceExtension implements INotificationServiceExtension {
@Override
public void onNotificationReceived(INotificationReceivedEvent event) {
IDisplayableMutableNotification notification = event.getNotification();
if (notification.getActionButtons() != null) {
for (IActionButton button : notification.getActionButtons()) {
// you can modify your action buttons here
}
}
// this is an example of how to modify the notification by changing the background color to blue
notification.setExtender(builder -> builder.setColor(0xFF0000FF));
//If you need to perform an async action or stop the payload from being shown automatically,
//use event.preventDefault(). Using event.notification.display() will show this message again.
}
}
2. Add the following to your AndroidManifest.xml
.
AndroidManifest.xml
.Add OneSignal class name and your class value as meta-data
within the AndroidManifest.xml
file under the application tag. Ignore any "unused" warnings.
<application ...>
<!-- name doesn't change, value = your class fully name spaced-->
<meta-data android:name="com.onesignal.NotificationServiceExtension"
android:value="com.onesignal.example.NotificationServiceExtension" />
</application>
Getting a payload from a notification
Major Release SDKs (4.x.x) see the OSNotification class
iOS Notification Service Extension
The UNNotificationServiceExtension allows you to modify the content of push notifications before they are displayed to the user and is required for other important features like:
- Images & Rich Media.
- Confirmed Delivery
- Badges
- Action Buttons
- Influenced Opens with Firebase Analytics
You likely set this up already if you followed our Mobile SDK setup instructions for your app, but this section will explain how to access the OneSignal notification payload data and troubleshoot any issues you might be having.
Getting a payload from an iOS notification
When following the Mobile SDK setup you will get to the section on adding the code to the OneSignalNotificationServiceExtension.
In that code, there is the method OneSignalExtension.didReceiveNotificationExtensionRequest
. This is where we pass the bestAttemptContent
of the notification to the app before it is displayed to the user. Before this method is called, you can get the notification payload and (if desired) update it before it is displayed to the user.
In this example, we send a notification with the following data:
//Replace with your own app data:
//YOUR_API_KEY, YOUR_APP_ID, SUBSCRIPTION_ID_1
curl --request POST \
--url 'https://api.onesignal.com/notifications' \
--header 'Authorization: Key YOUR_API_KEY' \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--data '
{
"app_id": "YOUR_APP_ID",
"target_channel": "push",
"headings": {"en": "The message title"},
"contents": {"en": "The message contents"},
"data":{"additional_data_key_1":"value_1","additional_data_key_2":"value_2"},
"include_subscription_ids": [
"SUBSCRIPTION_ID_1"
]
}
'
We can access this additional data within the OneSignalNotificationServiceExtension within OneSignal's custom
object using the a
parameter.
// Check if `bestAttemptContent` exists
if let bestAttemptContent = bestAttemptContent {
// Try to retrieve the "custom" data from the notification payload
if let customData = bestAttemptContent.userInfo["custom"] as? [String: Any],
let additionalData = customData["a"] as? [String: Any] {
// Convert the `additionalData` dictionary to a JSON string for logging
if let jsonData = try? JSONSerialization.data(withJSONObject: additionalData, options: .prettyPrinted),
let jsonString = String(data: jsonData, encoding: .utf8) {
// Successfully converted to JSON; log the formatted JSON string
print("The additionalData dictionary in JSON format:\n\(jsonString)")
} else {
// Failed to convert the `additionalData` dictionary to JSON
print("Failed to convert additionalData to JSON format.")
}
}
// Try to retrieve the "aps" data from the notification payload
if let messageData = bestAttemptContent.userInfo["aps"] as? [String: Any],
let apsData = messageData["alert"] as? [String: Any],
let body = apsData["body"] as? String, // Extract the "body" of the alert
let title = apsData["title"] as? String { // Extract the "title" of the alert
// Successfully retrieved the body and title; log the values
print("The message contents is: \(body), message headings is: \(title)")
} else {
// Failed to retrieve the "aps" data or its contents
print("Unable to retrieve apsData")
}
// Pass the notification to OneSignal for further processing
OneSignalExtension.didReceiveNotificationExtensionRequest(self.receivedRequest,
with: bestAttemptContent,
withContentHandler: self.contentHandler)
}
if (bestAttemptContent) {
// Retrieve and log customData["a"]
NSDictionary *customData = bestAttemptContent.userInfo[@"custom"];
if ([customData isKindOfClass:[NSDictionary class]]) {
NSDictionary *additionalData = customData[@"a"];
if ([additionalData isKindOfClass:[NSDictionary class]]) {
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:additionalData options:NSJSONWritingPrettyPrinted error:&error];
if (jsonData) {
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
NSLog(@"The additionalData dictionary in JSON format:\n%@", jsonString);
} else {
NSLog(@"Failed to convert additionalData to JSON format: %@", error.localizedDescription);
}
}
}
// Retrieve and log apsData
NSDictionary *messageData = bestAttemptContent.userInfo[@"aps"];
if ([messageData isKindOfClass:[NSDictionary class]]) {
NSDictionary *apsData = messageData[@"alert"];
if ([apsData isKindOfClass:[NSDictionary class]]) {
NSString *body = apsData[@"body"];
NSString *title = apsData[@"title"];
if ([body isKindOfClass:[NSString class]] && [title isKindOfClass:[NSString class]]) {
NSLog(@"The message content is: %@, message heading is: %@", body, title);
}
} else {
NSLog(@"Unable to retrieve apsData");
}
}
// Call OneSignalExtension method
[OneSignalExtension didReceiveNotificationExtensionRequest:self.receivedRequest
withNotification:bestAttemptContent
withContentHandler:self.contentHandler];
}
Example console output:
The additionalData dictionary in JSON format:
{
"additional_data_key_1" : "value_1",
"additional_data_key_2" : "value_2"
}
The message contents is: The message contents, message headings is: The message title
Troubleshooting the iOS Notification Service Extension
This guide is for debugging issues with Images, Action Buttons, or Confirmed Deliveries not showing on iOS mobile apps.
Check your Xcode Settings
In General > Targets Make sure your main app target and OneSignalNotificationServiceExtension** make sure you have the correct "Supported Destinations" and your "Minimum Deployment" is set to iOS 14.5 or higher.
- If you are using Cocoapods make sure these match with your main target in the Podfile to avoid build errors.
Continuing in the OneSignalNotificationServiceExtension > Info tab, expand the NSExtension
key. Ensure you see:
Swift
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.usernotifications.service</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).NotificationService</string>
</dict>
Objective-C
Same as above, except instead of $(PRODUCT_MODULE_NAME).NotificationService
Objective-C should be NotificationService
.
Turn off "Copy only when installing"
Select your main app target > Build Phases > Embed App Extensions. Ensure "Copy only when installing" is NOT checked. Uncheck it if it is:
How to debug the Notification Service Extension not running
1. Update the OneSignalNotificationServiceExtension code
Open the NotificationService.m
or NotificationService.swift
and replace the whole file contents with the below code. (The code is the same as our original setup code, just adding some additional logging.
Make sure to replace YOUR_BUNDLE_ID
with your actual Bundle ID.
import UserNotifications
import OneSignalExtension
import os.log
class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)?
var receivedRequest: UNNotificationRequest!
var bestAttemptContent: UNMutableNotificationContent?
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
self.receivedRequest = request
self.contentHandler = contentHandler
self.bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
let userInfo = request.content.userInfo
let custom = userInfo["custom"]
print("Running NotificationServiceExtension: userInfo = \(userInfo.description)")
print("Running NotificationServiceExtension: custom = \(custom.debugDescription)")
//debug log types need to be enabled in Console > Action > Include Debug Messages
os_log("%{public}@", log: OSLog(subsystem: "com.onesignal.OneSignal-Swift-Sample", category: "OneSignalNotificationServiceExtension"), type: OSLogType.debug, userInfo.debugDescription)
if let bestAttemptContent = bestAttemptContent {
/* DEBUGGING: Uncomment the 2 lines below to check this extension is executing
Note, this extension only runs when mutable-content is set
Setting an attachment or action buttons automatically adds this */
print("Running NotificationServiceExtension")
bestAttemptContent.body = "[Modified] " + bestAttemptContent.body
OneSignalExtension.didReceiveNotificationExtensionRequest(self.receivedRequest, with: bestAttemptContent, withContentHandler: self.contentHandler)
}
}
override func serviceExtensionTimeWillExpire() {
// Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
OneSignalExtension.serviceExtensionTimeWillExpireRequest(self.receivedRequest, with: self.bestAttemptContent)
contentHandler(bestAttemptContent)
}
}
}
#import <OneSignalExtension/OneSignalExtension.h>
#import "NotificationService.h"
@interface NotificationService ()
@property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
@property (nonatomic, strong) UNNotificationRequest *receivedRequest;
@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;
@end
@implementation NotificationService
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
self.receivedRequest = request;
self.contentHandler = contentHandler;
self.bestAttemptContent = [request.content mutableCopy];
//If your SDK version is < 3.5.0 uncomment and use this code:
/*
[OneSignal didReceiveNotificationExtensionRequest:self.receivedRequest
withMutableNotificationContent:self.bestAttemptContent];
self.contentHandler(self.bestAttemptContent);
*/
NSUserDefaults *userDefault = [[NSUserDefaults alloc] initWithSuiteName:@"group.YOUR_BUNDLE_ID.onesignal"];
NSLog(@"NSE player_id: %@", [userDefault stringForKey:@"GT_PLAYER_ID"]);
NSLog(@"NSE app_id: %@", [userDefault stringForKey:@"GT_APP_ID"]);
/* DEBUGGING: Uncomment the 2 lines below and comment out the one above to ensure this extension is excuting
Note, this extension only runs when mutable-content is set
Setting an attachment or action buttons automatically adds this */
NSLog(@"Running NotificationServiceExtension");
self.bestAttemptContent.body = [@"[Modified] " stringByAppendingString:self.bestAttemptContent.body];
[OneSignal.Debug setLogLevel:ONE_S_LL_VERBOSE];
[OneSignal didReceiveNotificationExtensionRequest:self.receivedRequest
withMutableNotificationContent:self.bestAttemptContent
withContentHandler:self.contentHandler];
}
- (void)serviceExtensionTimeWillExpire {
// Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
[OneSignal serviceExtensionTimeWillExpireRequest:self.receivedRequest withMutableNotificationContent:self.bestAttemptContent];
self.contentHandler(self.bestAttemptContent);
}
@end
2. Change your Active Scheme
Set your Active Scheme to the OneSignalNotificationServiceExtension
.
3. Build and run the project
Build and run the project in Xcode on a real device.
4. Open the Console
In Xcode, select Window > Devices and Simulators.
You should see your device connected. Select Open Console.
5. Check the Console
In the Console:
- Select Action > Include Debug Messages
- Search for
OneSignalNotificationServiceExtension
as the CATEGORY - Select Start
Send this device a notification with a message (use contents
property if sending from the Create message API). In this example, the payload is:
//Replace with your own app data:
//YOUR_API_KEY, YOUR_APP_ID, SUBSCRIPTION_ID_1
curl --request POST \
--url 'https://api.onesignal.com/notifications' \
--header 'Authorization: Key YOUR_API_KEY' \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--data '
{
"app_id": "YOUR_APP_ID",
"target_channel": "push",
"headings": {"en": "The message title"},
"contents": {"en": "The message contents"},
"data":{"additional_data_key_1":"value_1","additional_data_key_2":"value_2"},
"include_subscription_ids": [
"SUBSCRIPTION_ID_1"
]
}
'
You should see a message logged with the app running and not running.
If you do not see a message, then remove OneSignal from your app and follow our Mobile SDK Setup again to verify you setup OneSignal correctly.
Updated 1 day ago