Send push notifications
One of the features that PubNub offers is the ability to connect to Android and iOS push notification services.
This is done by creating a key from one, or both, of those providers and adding it to your PubNub Admin Portal.
It's possible to use Google/Firebase in iOS applications, so the choice is yours whether you want to manage both services.
Request a push notification token for the device
For every device that you'd like to receive a push notification on, you must register with the notification provider. Refer to the Android, iOS, and Firebase for iOS documentation on how to get this set up.
Register and cache your token
Now that you're set up with your token, you'll need to make sure as tokens are created by your service, that your app is always using the correct token. This means collecting, replacing, and caching. For caching you can use UserDefaults
for iOS and SharedPreferences
for Android.
To create the cache:
let tokenDispatch = DispatchQueue(label: "com.pubnub.deviceToken", attributes: .concurrent)
var cachedToken: Data? {
get {
var token: Data?
tokenDispatch.sync {
token = UserDefaults.standard.data(forKey: "com.pubnub.deviceToken")
}
return token
}
set {
tokenDispatch.async(flags: .barrier) {
UserDefaults.standard.set(newValue, forKey: "com.pubnub.deviceToken")
}
}
}
static dispatch_queue_t deviceTokenDispatch;
static dispatch_queue_t pushChannelsDispatch;
static dispatch_once_t registerCustomDispatchOnceToken;
- (void) registerCustomDispatchQueues {
dispatch_once(®isterCustomDispatchOnceToken, ^{
deviceTokenDispatch = dispatch_queue_create("com.pubnub.deviceToken", DISPATCH_QUEUE_CONCURRENT);
pushChannelsDispatch = dispatch_queue_create("com.pubnub.pushChannels", DISPATCH_QUEUE_CONCURRENT);
});
}
- (nullable NSData *) cachedToken {
__block NSData* token;
dispatch_sync(tokenDispatch, ^{
token = [[NSUserDefaults standardUserDefaults] dataForKey: @"com.pubnub.deviceToken"];
});
return token;
}
- (void) setCachedToken:(nullable NSData *) newValue {
dispatch_barrier_async(tokenDispatch, ^{
[[NSUserDefaults standardUserDefaults] setValue:newValue forKey:@"com.pubnub.deviceToken"];
});
}
public class SharedPreferencesManager {
private static SharedPreferences sharedPref;
private SharedPreferencesManager() { }
public static void init(Context context) {
if(sharedPref == null)
sharedPref = context.getSharedPreferences(context.getPackageName(), Context.MODE_PRIVATE);
}
public static final String FCM_DEVICE_TOKEN = "PUBNUB_FCM_DEVICE_TOKEN";
public static @Nullable String readDeviceToken() {
return sharedPref.getString(FCM_DEVICE_TOKEN, null)
}
public static void writeDeviceToken(String value) {
SharedPreferences.Editor prefsEditor = sharedPref.edit();
prefsEditor.putString(FCM_DEVICE_TOKEN, value);
prefsEditor.apply();
}
public static final String FCM_CHANNEL_LIST = "PUBNUB_FCM_CHANNEL_LIST";
public static @Nullable String[] readChannelList() {
return (String[]) sharedPref.getStringSet(FCM_CHANNEL_LIST, null).toArray();
}
public static void writeChannelList(Set<String> value) {
SharedPreferences.Editor prefsEditor = sharedPref.edit();
prefsEditor.putStringSet(FCM_CHANNEL_LIST, value);
prefsEditor.apply();
}
}
class SharedPreferencesManager private constructor() {
companion object {
private val sharePref = SharedPreferencesManager()
private lateinit var sharedPreferences: SharedPreferences
private val PLACE_OBJ = "place_obj"
private val FCM_DEVICE_TOKEN = "PUBNUB_FCM_DEVICE_TOKEN"
private val FCM_CHANNEL_LIST = "PUBNUB_FCM_CHANNEL_LIST"
fun getInstance(context: Context): SharedPreferencesManager {
if (!::sharedPreferences.isInitialized) {
synchronized(SharedPreferencesManager::class.java) {
if (!::sharedPreferences.isInitialized) {
sharedPreferences = context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
}
}
}
return sharePref
}
}
fun readDeviceToken(): String? = sharedPreferences.getString(FCM_DEVICE_TOKEN, null)
fun writeDeviceToken(value: String?) = sharedPreferences.edit().putString(FCM_DEVICE_TOKEN, value).apply()
fun readChannelList(): List<String> = sharedPreferences.getStringSet(FCM_CHANNEL_LIST, null)?.toList().orEmpty()
fun writeChannelList(value: Set<String>?) = sharedPreferences.edit().putStringSet(FCM_CHANNEL_LIST, value).apply()
}
Now, let's catch and replace the tokens as they come in.
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let oldDeviceToken = self.cachedToken
guard deviceToken != oldDeviceToken else { return }
self.cachedToken = deviceToken
updateAPNSDevicesOnChannels(
pushChannels.allObjects, newDevice: deviceToken, oldDevice: oldDeviceToken,
on: "com.mycompany.mybundleid", environment: .production
) { result in
switch result {
case .success: print("Successfully updated channels with new token")
case let .failure(error): print("Failed to update device token due to: \(error)")
}
}
}
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
NSData *oldDevice = self.cachedToken;
if ([deviceToken isEqualToData:oldDevice]) { return; }
self.cachedToken = deviceToken;
[self updateAPNSDevicesOnChannels:[self.pushChannels allObjects]
withDevicePushToken:deviceToken
replacingDevicePushToken:oldDevice
pushType:PNAPNS2Push
environment:PNAPNSProduction
topic:@"com.mycompany.mybundleid"
andCompletion:^(PNAcknowledgmentStatus * _Nonnull status) {
if (!status.isError) {
NSLog(@"Successfully updated channels with new token");
} else {
NSLog(@"Failed to update device token due to: %@", status);
}
}];
}
public class MyFirebaseMessagingService extends FirebaseMessagingService {
@Override public void onNewToken(String token) {
String oldToken = SharedPreferencesManager.readDeviceToken();
if (token.equals(oldToken)) { return; }
SharedPreferencesManager.write(token);
updatePushNotificationsOnChannels(SharedPreferencesManager.readChannelList(), token, oldToken);
}
}
override fun onNewToken(token: String) {
val oldToken = SharedPreferencesManager.getInstance(applicationContext).readDeviceToken()
if (token == oldToken) { return }
SharedPreferencesManager.getInstance(applicationContext).writeDeviceToken(token)
updatePushNotificationsOnChannels(
SharedPreferencesManager.getInstance(applicationContext).readChannelList(),
token,
oldToken
)
}
Connect the device to a channel
For your app to know that what you're sending is going to be a push notification, you'll need to define certain channels to be push channels. In the code examples below you'll see Ch1
and Ch2
as the two channels being added.
if let deviceToken = (UIApplication.shared.delegate as? AppDelegate)?.cachedToken {
pubnub.addAPNSDevicesOnChannels(
["ch1", "ch2"],
device: deviceToken,
on: "com.mycompany.mybundleid",
environment: .production
) { result in
switch result {
case let .success(channelsAdded):
channelsAdded.forEach { (UIApplication.shared.delegate as? AppDelegate)?.pushChannels.update(with: $0) }
case let .failure(error): print("Failed to add Push due to: \(error.localizedDescription)")
}
}
}
NSData *deviceToken = [(AppDelegate *)[[UIApplication sharedApplication] delegate] cachedToken];
if (deviceToken == nil) { return; }
[self.client addPushNotificationsOnChannels:@[@"ch1",@"ch2"]
withDevicePushToken:deviceToken
pushType:PNAPNS2Push
environment:PNAPNSProduction
topic:@"com.mycompany.mybundleid"
andCompletion:^(PNAcknowledgmentStatus *status) {
if (!status.isError) {
NSSet* cachedChannels = [(AppDelegate *)[[UIApplication sharedApplication] delegate] pushChannels];
[(AppDelegate *)[[UIApplication sharedApplication] delegate] setPushChannels:[cachedChannels setByAddingObjectsFromArray:@[@"ch1",@"ch2"]]];
} else { NSLog(@"Failed to add Push due to: %@", status); }
}];
String cachedToken = SharedPreferencesManager.readDeviceToken();
pubnub.addPushNotificationsOnChannels()
.pushType(PNPushType.FCM)
.deviceId(cachedToken)
.channels(Arrays.asList("ch1", "ch2", "ch3"))
.async(new PNCallback<PNPushAddChannelResult>() {
@Override
public void onResponse(PNPushAddChannelResult result, PNStatus status) {
// Handle Response
}
});
SharedPreferencesManager.getInstance(applicationContext).readDeviceToken()?.also { deviceToken ->
pubnub.addPushNotificationsOnChannels(
pushType = PNPushType.FCM,
deviceId = deviceToken,
channels = listOf("ch1", "ch2", "ch3")
).async { result, status ->
// Handle Response
}
}
Building the push notification message
When constructing the Push message you wish to send you need to specify whether you're using Apple Push Notification pn_apns
or Firebase (Google) Cloud Message pn_gcm
in your payload. APN will require you to define the environment (development
or production
) and the Bundle ID of your app using topic
.
For Apple Push Notification Service:
{
"text": "John invited you to chat",
"pn_apns": {
"aps": {
"alert": {
"title": "Chat Invitation",
"body": "John invited you to chat"
}
},
"pn_push":[
{
"push_type": "alert",
"auth_method": "token",
"targets":[
{
"environment":"production",
"topic":"com.mycompany.mybundleid",
"excluded_devices": ["device-token1", "device-token2"]
}
],
"version":"v2"
}
]
}
}
let message = ["text": "John invited you to chat"]
let pushPayload = PubNubPushMessage(
apns: PubNubAPNSPayload(
aps: APSPayload(alert: .object(.init(title: "Chat Invitation", body: "John invited you to chat"))),
pubnub: [.init(targets: [.init(topic: "com.mycompany.mybundleid", environment: .production)])],
),
additional: message
)
NSDictionary *message = @{
@"text": @"John invited you to chat"
};
PNNotificationsPayload *pushData = [PNNotificationsPayload
payloadsWithNotificationTitle:@"Chat Invitation"
body:@"John invited you to chat"];
// create the APNs target with topic and environment
PNAPNSNotificationTarget *target = [PNAPNSNotificationTarget
targetForTopic:@"com.mycompany.mybundleid"
inEnvironment:PNAPNSProduction
withExcludedDevices:nil];
// create the APNs config object and add the target
PNAPNSNotificationConfiguration *apnsConfig =
[PNAPNSNotificationConfiguration configurationWithTargets:@[target]];
// add the APNs config to the main pushData object
pushData.apns.configurations = @[apnsConfig];
// generate the final pushPayload object
NSDictionary *pushPayload = [pushData dictionaryRepresentationFor:PNAPNS2Push|PNFCMPush];
For Firebase (Including Firebase for iOS):
{
"text": "John invited you to chat",
"pn_gcm": {
"topic": "invitations",
"apns": {
"payload": {
"aps": {
"alert": {
"title": "Chat Invitation",
"body": "John invited you to chat"
}
}
},
"headers": {
"apns-push-type": "alert",
"apns-topic": "com.mycompany.mybundleid",
"apns-priority": "10"
}
},
"pn_exceptions" : ["device-token1", "device-token2"]
}
}
let message = ["text": "John invited you to chat"]
let pushPayload = PubNubPushMessage(
fcm: PubNubFCMPayload(
payload: nil,
target: .topic("invitations"),
apns: FCMApnsConfig(
headers: [
"apns-push-type": "alert", "apns-topic": "com.mycompany.mybundleid", "apns-priority": "10"
],
payload: APSPayload(alert: .object(.init(title: "Chat Invitation", body: "John invited you to chat")))
)
),
additional: message
)
NSDictionary *message = @{
@"text": @"John invited you to chat"
};
NSDictionary *pushPayload = @{
@"text": @"John invited you to chat",
@"pn_gcm": @{
@"to": @"invitations",
@"apns": @{
@"payload": @{
@"aps": @{
@"alert": @{
@"title": @"Chat Invitation",
@"body": @"John invited you to chat"
}
},
},
@"headers": @{
@"apns-push-type": @"alert",
@"apns-topic": @"com.mycompany.mybundleid",
@"apns-priority": @"10"
}
},
@"pn_exceptions": @[@"device-token1", @"device-token2"]
}
};
{
"text": "John invited you to chat",
"pn_gcm": {
"topic": "invitations",
"android": {
"notification": {
"title": "Chat Invitation",
"body": "John invited you to chat"
}
},
"pn_exceptions" : ["device-token1", "device-token2"]
}
}
PushPayloadHelper pushPayloadHelper = new PushPayloadHelper();
PushPayloadHelper.FCMPayload fcmPayload = new PushPayloadHelper.FCMPayload();
PushPayloadHelper.FCMPayload.Notification fcmNotification =
new PushPayloadHelper.FCMPayload.Notification()
.setTitle("Chat Invitation")
.setBody("John invited you to chat");
fcmPayload.setNotification(fcmNotification);
pushPayloadHelper.setFcmPayload(fcmPayload);
Map<String, Object> commonPayload = new HashMap<>();
commonPayload.put("text", "John invited you to chat");
pushPayloadHelper.setCommonPayload(commonPayload);
Map<String, Object> pushPayload = pushPayloadHelper.build();
val pushPayloadHelper = PushPayloadHelper()
val fcmPayload = FCMPayload().apply {
notification = FCMPayload.Notification().apply {
title = "Chat Invitation"
body = "John invited you to chat"
}
custom = mapOf(
"topic" to "invitations",
"pn_exceptions" to arrayOf("device-token1", "device-token2")
)
}
pushPayloadHelper.fcmPayload = fcmPayload
pushPayloadHelper.commonPayload = mapOf(
"John invited you to chat" to "text"
)
val pushPayload = pushPayloadHelper.build()
Send the Push Notification
Just like when sending any other message with PubNub, we use the publish method to push notifications. For push notifications to work we must send a message on the channels that we registered for push notifications in the previous steps.
pubnub.publish()
.channel("ch1")
.message(pushPayload)
.async(new PNCallback<PNPublishResult>() {
@Override
public void onResponse(PNPublishResult result, PNStatus status) {
// Handle Response
}
});
pubnub.publish(
channel = "ch1",
message = pushPayload
).async { result, status ->
// Handle Response
}
//publish on channel
pubnub.publish(
{
channel: "ch1"
message: pushPayload
},
function (status, response) {
// Handle Response
}
);
pubnub.publish(channel: "ch1", message: pushPayload) { result in
switch result {
case let .success(timetoken):
print("Successfully published message at: \(timetoken)")
case let .failure(error):
print("Failed to publish due to: \(error)")
}
}
[self.pubnub publish:message toChannel:@"ch1" mobilePushPayload:pushPayload
withCompletion:^(PNPublishStatus *status) {
if (!status.isError) {
NSLog(@"Successfully published message");
} else {
NSLog(@"Failed to publish due to: %@", status);
}
}];