Push Notifications¶
The remote notification client class helps your mobile applications use push notification capabilities provided by SAP Mobile Services. First, the mobile applications will need to obtain a push token from the FCM infrastructure and register it with the mobile services Push service. Once the Push token is registered, the mobile services administration infrastructure can be used to send the notification to devices.
Setting up FCM Capability for an Application¶
To enable FCM for your application, follow the Google documentation at Set Up a Firebase Cloud Messaging Client App on Android.
Note
Firebase Cloud Messaging (FCM) is the new version of GCM. It inherits the reliable and scalable GCM infrastructure and provides new features. See the FAQ to learn more. If you are integrating messaging in a new app, start with FCM. GCM users are strongly recommended to upgrade to FCM in order to benefit from new FCM features today and in the future.
You can find how to set up GCM at Set up a Firebase Cloud Messaging client app on Android.
Notifications¶
Registering a Device Token¶
Device tokens are the UUID codes generated by the FCM. The device token obtained from push infrastructures such as FCM should be registered with mobile services, using the registerDeviceToken
method of the RemoteNotificationClient
class.
This method must be invoked from the UI thread. It takes the device token and RemoteNotificationParameters
as parameters.
The callback methods are invoked on the UI thread.
RemoteNotificationParameters parameters = new RemoteNotificationParameters.Builder()
.lastKnownLocation(new RemoteNotificationParameters.LastKnownLocation("32.80776", "12.456546"))
.build();
RemoteNotificationClient remoteNotificationClient = new RemoteNotificationClient();
remoteNotificationClient.registerDeviceToken(token,parameters,
new RemoteNotificationClient.CallbackListener() {
@Override
public void onSuccess() {
//Here goes the code for processing successful response...
}
@Override
public void onError(Throwable result) {
//Handle your failure handling code here...
if (result instanceof HttpException) {
//HttpException type com.sap.cloud.mobile.foundation.networking.HttpException
HttpException ne = (HttpException)result;
Log.e("Http Exception: " , ne.message() + ", with Error code: " + ne.code());
} else {
Log.e("Exception occurred: ", result.getMessage());
}
}
}
);
val parameters = RemoteNotificationParameters.Builder()
.lastKnownLocation(RemoteNotificationParameters.LastKnownLocation("32.80776", "12.456546"))
.build()
val remoteNotificationClient = RemoteNotificationClient()
remoteNotificationClient.registerDeviceToken(token, parameters,
object : RemoteNotificationClient.CallbackListener {
override fun onSuccess() {
//Here goes the code for processing successful response...
}
override fun onError(result: Throwable) {
//Handle your failure handling code here...
if (result is HttpException) {
//HttpException type com.sap.cloud.mobile.foundation.networking.HttpException
Log.e("Http Exception: ", result.message() + ", with Error code: " + result.code())
} else {
Log.e("Exception occurred: ", result.message)
}
}
}
)
Unregistering a Device Token¶
To disable notifications for mobile applications, delete the device token from the mobile service using the
unregisterDeviceToken
method. Once the unregistration is done, the server will not be
able to send a push notification to the device.
This method must be invoked from the UI thread. The callback methods are invoked on the UI thread.
RemoteNotificationClient remoteNotificationClient = new RemoteNotificationClient();
remoteNotificationClient.unregisterDeviceToken(
new RemoteNotificationClient.CallbackListener() {
@Override
public void onSuccess() {
//Here goes the code for processing successful response...
}
@Override
public void onError(Throwable result) {
//Handle error here...
if (result instanceof HttpException) {
//HttpException type com.sap.cloud.mobile.foundation.networking.HttpException
HttpException ne = (HttpException)result;
Log.e("Http Exception: " , ne.message() + ", with Error code: " + ne.code());
} else {
Log.e("Exception occurred: ", result.getMessage());
}
}
}
);
val remoteNotificationClient = RemoteNotificationClient()
remoteNotificationClient.unregisterDeviceToken(
object : RemoteNotificationClient.CallbackListener {
override fun onSuccess() {
//Here goes the code for processing successful response...
}
override fun onError(result: Throwable) {
//Handle error here...
if (result is HttpException) {
//HttpException type com.sap.cloud.mobile.foundation.networking.HttpException
Log.e("Http Exception: ", result.message() + ", with Error code: " + result.code())
} else {
Log.e("Exception occurred: ", result.message)
}
}
}
)
Notification Feedback¶
SAP Mobile Services provides a feedback service that a mobile application can use to acknowledge the
notification. Every notification message from the service carries a notificationid
that uniquely identifies the
notification. Using this unique identification value, the updateNotificationStatus
method can be used to update the
notification status.
The mobile service supports three statuses for feedback: NOTIFICATIONSTATUS.RECEIVED, NOTIFICATIONSTATUS.CONFIRMED and NOTIFICATIONSTATUS.CONSUMED. The code sample below shows how to provide received feedback.
This method must be invoked from the UI thread. It takes the device notification id and SettingsParameters
as parameters.
The callback methods are invoked on the UI thread.
RemoteNotificationClient remoteNotificationClient = new RemoteNotificationClient();
remoteNotificationClient.updateNotificationStatus(notificationId, RemoteNotificationClient.NOTIFICATIONSTATUS.RECEIVED,
new RemoteNotificationClient.CallbackListener() {
@Override
public void onSuccess() {
//Here goes the code for processing successful response...
}
@Override
public void onError(Throwable result) {
//Handle error here...
if (result instanceof HttpException) {
//HttpException type com.sap.cloud.mobile.foundation.networking.HttpException
HttpException ne = (HttpException)result;
Log.e("Http Exception: " , ne.message() + ", with Error code: " + ne.code());
} else {
Log.e("Exception occurred: ", result.getMessage());
}
}
}
);
val remoteNotificationClient = RemoteNotificationClient()
remoteNotificationClient.updateNotificationStatus(notificationId, RemoteNotificationClient.NOTIFICATIONSTATUS.RECEIVED,
object : RemoteNotificationClient.CallbackListener {
override fun onSuccess() {
//Here goes the code for processing successful response...
}
override fun onError(result: Throwable) {
//Handle error here...
if (result is HttpException) {
//HttpException type com.sap.cloud.mobile.foundation.networking.HttpException
Log.e("Http Exception: ", result.message() + ", with Error code: " + result.code())
} else {
Log.e("Exception occurred: ", result.message)
}
}
}
)
Support for Baidu Push¶
The remote notification API can be used to support the Baidu push service. Register and unregister the device token by specifying Baidu as the push service using SettingsParameter
. The settings parameter should be configured
prior to creating a RemoteNotificationClient
instance.
The following example shows how to register a Baidu push token to mobile services:
RemoteNotificationParameters parameters = new RemoteNotificationParameters.Builder()
.lastKnownLocation(new RemoteNotificationParameters.LastKnownLocation("32.80776", "12.456546"))
.build();
//This following line specify that the target we are using is for BAIDU
settingsParameters.setPushService(SettingsParameters.PushService.BAIDU);
RemoteNotificationClient remoteNotificationClient = new RemoteNotificationClient();
remoteNotificationClient.registerDeviceToken(token,parameters,
new RemoteNotificationClient.CallbackListener() {
@Override
public void onSuccess() {
//Here goes the code for processing successful response...
}
@Override
public void onError(Throwable result) {
//Handle your failure handling code here...
if (result instanceof HttpException) {
//HttpException type com.sap.cloud.mobile.foundation.networking.HttpException
HttpException ne = (HttpException)result;
Log.e("Http Exception: " , ne.message() + ", with Error code: " + ne.code());
} else {
Log.e("Exception occurred: ", result.getMessage());
}
}
}
);
val parameters = RemoteNotificationParameters.Builder()
.lastKnownLocation(RemoteNotificationParameters.LastKnownLocation("32.80776", "12.456546"))
.build()
//This following line specify that the target we are using is for BAIDU
settingsParameters.pushService = SettingsParameters.PushService.BAIDU
val remoteNotificationClient = RemoteNotificationClient()
remoteNotificationClient.registerDeviceToken(token, parameter,
object : RemoteNotificationClient.CallbackListener {
override fun onSuccess() {
//Here goes the code for processing successful response...
}
override fun onError(result: Throwable) {
//Handle your failure handling code here...
if (result is HttpException) {
//HttpException type com.sap.cloud.mobile.foundation.networking.HttpException
Log.e("Http Exception: ", result.message() + ", with Error code: " + result.code())
} else {
Log.e("Exception occurred: ", result.message)
}
}
}
)
The following example shows how to unregister the Baidu push token from mobile services:
//This following line specify that the target we are using is for BAIDU
settingsParameters.setPushService(SettingsParameters.PushService.BAIDU);
RemoteNotificationClient remoteNotificationClient = new RemoteNotificationClient();
remoteNotificationClient.unregisterDeviceToken(
new RemoteNotificationClient.CallbackListener() {
@Override
public void onSuccess() {
//Here goes the code for processing successful response...
}
@Override
public void onError(Throwable result) {
//Handle error here...
if (result instanceof HttpException) {
//HttpException type com.sap.cloud.mobile.foundation.networking.HttpException
HttpException ne = (HttpException)result;
Log.e("Http Exception: " , ne.message() + ", with Error code: " + ne.code());
} else {
Log.e("Exception occurred: ", result.getMessage());
}
}
}
);
//This following line specify that the target we are using is for BAIDU
settingsParameters.pushService = SettingsParameters.PushService.BAIDU
val remoteNotificationClient = RemoteNotificationClient()
remoteNotificationClient.unregisterDeviceToken(
object : RemoteNotificationClient.CallbackListener {
override fun onSuccess() {
//Here goes the code for processing successful response...
}
override fun onError(result: Throwable) {
//Handle error here...
if (result is HttpException) {
//HttpException type com.sap.cloud.mobile.foundation.networking.HttpException
Log.e("Http Exception: ", result.message() + ", with Error code: " + result.code())
} else {
Log.e("Exception occurred: ", result.message)
}
}
}
)
Using PushService
to Register Push Notifications¶
The SAP Mobile Services Android SDK (3.0.0 and later) provides a new PushService
API, simplifying push registration.
Add the PushService
instance to the SDKInitializer.start
method to start the push service.
SDKInitializer.INSTANCE.start(application, new MobileService[]{new PushService()}, null);
SDKInitializer.start(application, PushService())
Using PushService
to Display Notification Messages¶
PushService
can add a listener to receive messages sent from the server. It can also customize the displayed notification messages.
PushService pushService = new PushService();
pushService.setPushCallbackListener((context, message) -> {
...
});
SDKInitializer.INSTANCE.start(application, new MobileService[]{pushService}, null);
val pushService = PushService().apply {
this.setPushCallbackListener { context, message ->
...
}
}
SDKInitializer.start(application, pushService)
Here is an example of how to implement the listener. The example shows how to handle messages in both foreground and background situations. For the background scenario, the message will toast by Android system notification and it will jump to your application when the message is clicked. In the foreground scenario, it will toast a dialog to notify the message regarding your current activity.
public class FCMPushCallbackListener implements PushCallbackListener {
@Override
public void onReceive(Context context, PushRemoteMessage message) {
// The following section is a sample for handling push messages. This could be customized
// according to the needs of the concrete application.
// ---------------------start of suggested customization--------------------------
String textMsg = context.getResources().getString(com.sap.cloud.mobile.foundation.R.string.push_text);
String notificationTitle = context.getResources().getString(com.sap.cloud.mobile.foundation.R.string.push_message);
String notificationID = message.getNotificationID();
if (message != null) {
if(message.getTitle() != null && !message.getTitle().isEmpty()){
notificationTitle = message.getTitle();
}else {
}
if(message.getAlert() != null && !message.getAlert().isEmpty()){
textMsg = message.getAlert();
}else {
}
}
if (isAppInBackground()) {
// background
Intent pushActivityStarter = new Intent(context.getApplicationContext(), PushNotificationActivity.class);
pushActivityStarter.putExtra(NOTIFICATION_DATA, message);
pushActivityStarter.putExtra(NOTIFICATION_ID_EXTRA,notificationID);
NotificationUtilities notUtils = new NotificationUtilities(context.getApplicationContext());
notUtils.showNotificationMessage(notificationTitle, textMsg, pushActivityStarter);
} else {
// foreground
Activity foregroundActivity = AppLifecycleCallbackHandler.getInstance().getActivity();
foregroundActivity.getIntent().putExtra(NOTIFICATION_ID_EXTRA, notificationID);
foregroundActivity.getIntent().putExtra(NOTIFICATION_DATA, message);
PushNotificationActivity.presentPushMessage(foregroundActivity, notificationID);
}
}
/**
* Method checks if the app is in background or not
*/
public boolean isAppInBackground() {
Lifecycle.State currentState = ProcessLifecycleOwner.get().getLifecycle().getCurrentState();
return !currentState.isAtLeast(Lifecycle.State.RESUMED);
}
}
class FCMPushCallbackListener : PushCallbackListener {
override fun onReceive(context: Context, message: PushRemoteMessage) {
// The following section is a sample for handling push messages. This could be customized
// according to the needs of the concrete application.
// ---------------------start of suggested customization--------------------------
var textMsg = context.resources.getString(com.sap.cloud.mobile.foundation.R.string.push_text)
var notificationTitle = context.resources.getString(com.sap.cloud.mobile.foundation.R.string.push_message)
val notificationID = message.notificationID
if (message != null) {
if (message.title != null) {
notificationTitle = message.title
} else {
}
if (message.alert != null) {
textMsg = message.alert
} else {
}
}
if (isAppInBackground) {
// background
val pushActivityStarter = Intent(context.applicationContext, PushNotificationActivity::class.java)
pushActivityStarter.putExtra(NOTIFICATION_DATA, message)
pushActivityStarter.putExtra(NOTIFICATION_ID_EXTRA, notificationID)
val notUtils = NotificationUtilities(context.applicationContext)
notUtils.showNotificationMessage(notificationTitle, textMsg, pushActivityStarter)
} else {
// foreground
val foregroundActivity = AppLifecycleCallbackHandler.getInstance().activity
foregroundActivity!!.intent.putExtra(NOTIFICATION_ID_EXTRA, notificationID)
foregroundActivity.intent.putExtra(NOTIFICATION_DATA, message)
PushNotificationActivity.presentPushMessage(foregroundActivity, notificationID)
}
}
/**
* Method checks if the app is in background or not
*/
val isAppInBackground: Boolean
get() {
val currentState = ProcessLifecycleOwner.get().lifecycle.currentState
return !currentState.isAtLeast(Lifecycle.State.RESUMED)
}
}
NotificationUtilities
code example:
public class NotificationUtilities {
private static final String PUSH_CHANNEL = "my_push_channel";
public static final String NOTIFICATION_ID_EXTRA = "NotificationID";
public static final String NOTIFICATION_DATA = "NotificationData";
private Context context;
private String negativeButton;
/**
* id to handle the notifications in the notification tray.
* This is declared static intentionally so that increment of this variable is shared with multiple background notifications.
* Each time a background notification is received, this value will be incremented to keep a unique id for the intents
*/
private static int notificationIdStart = 100;
public NotificationUtilities(Context context) {
this.context = context;
negativeButton = context.getApplicationContext().getResources().getString(R.string.cancel);
}
public void showNotificationMessage(final String title, final String message, Intent intent) {
// Check for empty push message
if (TextUtils.isEmpty(message))
return;
// status bar icon
final int smallIcon = R.mipmap.ic_statusbar;
// notification bar icon
final int largeIcon = R.mipmap.ic_launcher;
intent.putExtra(RemoteNotificationConfig.CURRENT_NOTIFICATION_ID, notificationIdStart );
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
final PendingIntent resultPendingIntent =
PendingIntent.getActivity(
context,
notificationIdStart,
intent,
PendingIntent.FLAG_UPDATE_CURRENT
);
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
// versions >= 26 we need to have notification channel, as well
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// user-visible name of the channel.
CharSequence name = "push-channel";
// user-visible description of the channel.
String description = "notification channel for push messages";
// importance
int importance = NotificationManager.IMPORTANCE_HIGH;
NotificationChannel notificationChannel = new NotificationChannel(PUSH_CHANNEL, name, importance);
notificationChannel.setDescription(description);
notificationChannel.enableLights(true);
// sets the notification light color for notifications posted to this
// channel, if the device supports this feature
notificationChannel.setLightColor(Color.RED);
notificationChannel.enableVibration(true);
notificationChannel.setVibrationPattern(new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400});
notificationManager.createNotificationChannel(notificationChannel);
}
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context, PUSH_CHANNEL);
showSmallNotification(notificationBuilder, smallIcon, largeIcon, title, message, resultPendingIntent);
}
private void showSmallNotification(NotificationCompat.Builder notificationBuilder, int smallIcon, int largeIcon, String title, String message, PendingIntent resultPendingIntent) {
NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();
inboxStyle.addLine(message);
Intent intentCancel = new Intent(context, PushNotificationActionReceiver.class);
intentCancel.setAction(negativeButton);
intentCancel.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intentCancel.putExtra(RemoteNotificationConfig.CURRENT_NOTIFICATION_ID,notificationIdStart );
//This Intent will be called when Cancel button from notification will be clicked by user.
PendingIntent pendingIntentCancel = PendingIntent.getBroadcast(context, notificationIdStart, intentCancel, PendingIntent.FLAG_CANCEL_CURRENT);
Notification notification = notificationBuilder
.setSmallIcon(smallIcon)
.setLargeIcon(BitmapFactory.decodeResource(context.getResources(), largeIcon))
.setTicker(title)
.setWhen(0)
.setAutoCancel(true)
.setContentTitle(title)
.setContentIntent(resultPendingIntent)
.setStyle(inboxStyle)
.setBadgeIconType(NotificationCompat.BADGE_ICON_SMALL)
.setContentText(message)
.addAction(R.drawable.ic_close_black_24dp, negativeButton, pendingIntentCancel)
.build();
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(notificationIdStart++, notification);
}
// Clears notification tray messages
public static void clearNotifications(Context context) {
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancelAll();
}
}
class NotificationUtilities(private val context: Context) {
private val negativeButton: String = context.applicationContext.resources.getString(R.string.cancel)
fun showNotificationMessage(title: String, message: String, intent: Intent) {
// Check for empty push message
if (TextUtils.isEmpty(message))
return
// status bar icon
val smallIcon = R.mipmap.ic_statusbar
// notification bar icon
val largeIcon = R.mipmap.ic_launcher
intent.putExtra(RemoteNotificationConfig.CURRENT_NOTIFICATION_ID, notificationIdStart)
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
val resultPendingIntent = PendingIntent.getActivity(
context,
notificationIdStart,
intent,
PendingIntent.FLAG_UPDATE_CURRENT
)
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
// versions >= 26 we need to have notification channel, as well
if (SDK_INT >= O) {
// user-visible name of the channel.
val name = "push-channel"
// user-visible description of the channel.
val description = "notification channel for push messages"
// importance
val importance = NotificationManager.IMPORTANCE_HIGH
val notificationChannel = NotificationChannel(PUSH_CHANNEL, name, importance)
notificationChannel.description = description
notificationChannel.enableLights(true)
// sets the notification light color for notifications posted to this
// channel, if the device supports this feature
notificationChannel.lightColor = Color.RED
notificationChannel.enableVibration(true)
notificationChannel.vibrationPattern = longArrayOf(100, 200, 300, 400, 500, 400, 300, 200, 400)
notificationManager.createNotificationChannel(notificationChannel)
}
val notificationBuilder: NotificationCompat.Builder = NotificationCompat.Builder(context, PUSH_CHANNEL)
showSmallNotification(notificationBuilder, smallIcon, largeIcon, title, message, resultPendingIntent)
}
private fun showSmallNotification(notificationBuilder: NotificationCompat.Builder, smallIcon: Int, largeIcon: Int, title: String, message: String, resultPendingIntent: PendingIntent) {
val inboxStyle = NotificationCompat.InboxStyle()
inboxStyle.addLine(message)
val intentCancel = Intent(context, PushNotificationActionReceiver::class.java)
intentCancel.action = negativeButton
intentCancel.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
intentCancel.putExtra(RemoteNotificationConfig.CURRENT_NOTIFICATION_ID, notificationIdStart)
//This Intent will be called when Cancel button from notification will be clicked by user.
val pendingIntentCancel = PendingIntent.getBroadcast(context, notificationIdStart, intentCancel, PendingIntent.FLAG_CANCEL_CURRENT)
val notification = notificationBuilder
.setSmallIcon(smallIcon)
.setLargeIcon(BitmapFactory.decodeResource(context.resources, largeIcon))
.setTicker(title)
.setWhen(0)
.setAutoCancel(true)
.setContentTitle(title)
.setContentIntent(resultPendingIntent)
.setStyle(inboxStyle)
.setBadgeIconType(NotificationCompat.BADGE_ICON_SMALL)
.setContentText(message)
.addAction(R.drawable.ic_close_black_24dp, negativeButton, pendingIntentCancel)
.build()
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.notify(notificationIdStart++, notification)
}
companion object {
private const val PUSH_CHANNEL = "my_push_channel"
val NOTIFICATION_ID_EXTRA = "NotificationID"
val NOTIFICATION_DATA = "NotificationData"
/**
* id to handle the notifications in the notification tray.
* This is declared static intentionally so that increment of this variable is shared with multiple background notifications.
* Each time a background notification is RECEIVED, this value will be incremented to keep a unique id for the intents
*/
private var notificationIdStart = 100
// Clears notification tray messages
fun clearNotifications(context: Context) {
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.cancelAll()
}
}
}
Using PushService
to Unregister Device Tokens¶
PushService
also provides the API to unregister device tokens from the server.
PushService.unregisterPush(new RemoteNotificationClient.CallbackListener() {
@Override
public void onSuccess() {
//handle success message
}
@Override
public void onError(@NonNull Throwable throwable) {
//handle failed message
}
});
PushService.unregisterPush(object : RemoteNotificationClient.CallbackListener {
override fun onSuccess() {
//handle success message
}
}
override fun onError(throwable: Throwable) {
//handle failed message
}
})
Using PushService
for Notification Feedback¶
The PushService
provides a simplified way to manage notifications.
PushService.setPushMessageStatus(notificationId, PushService.NotificationStatus.CONSUMED);
PushService.setPushMessageStatus(notificationId, PushService.NotificationStatus.CONSUMED)