The SDK automatically registers your app to receive push messages through FCM.
Should you need access to the raw push messages — for example to run custom logic over the data payload — you can obtain them by either registering a PushMessageListener when initialising the SDK, or by reading the extras placed on the launcher Activity's intent when the user taps a system-tray notification.
| App state | Notification | Data | Both |
|---|---|---|---|
| Foreground | PushMessageListener | PushMessageListener | PushMessageListener |
| Background | System tray | PushMessageListener | Notification: system tray; Data: in extras of the intent. |
PushMessageListener redirects messages from the FirebaseMessagingService that the SDK already registers internally — you do not need to (and should not) declare your own FirebaseMessagingService for the same intent filter, as that would replace the SDK's one.
Learn more in the Firebase documentation.
Android 13+ (API 33) requiresPOST_NOTIFICATIONSThe SDK declares
android.permission.POST_NOTIFICATIONSin its manifest, but on Android 13 and above your app must also request it at runtime before any notification will be shown. The OS silently drops notifications for apps that have not been granted the permission.
Deep linksIf a push notification is delivered while the app is in the foreground, you can build your own deep link in the
onMessageReceived()method that creates a link to a particular activity in your app.
Implementing PushMessageListener
public class PushHandler implements PushMessageListener {
private static final String CHANNEL_ID = "comapi_default_channel";
private final Context context;
public PushHandler(Context context) {
this.context = context.getApplicationContext();
}
@Override
public void onMessageReceived(RemoteMessage message) {
RemoteMessage.Notification notification = message.getNotification();
if (notification == null) {
// Data-only payload: handle silently or post your own notification.
return;
}
String title = notification.getTitle();
String body = notification.getBody();
Log.i("ComapiPush", "Push notification: " + body);
createNotificationChannel();
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
.setAutoCancel(true)
.setDefaults(NotificationCompat.DEFAULT_ALL)
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(title)
.setContentText(body);
NotificationManagerCompat manager = NotificationManagerCompat.from(context);
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS)
== PackageManager.PERMISSION_GRANTED) {
manager.notify(1, builder.build());
}
}
private void createNotificationChannel() {
// Notification channels were introduced in API 26. The SDK's minSdk is 16,
// so the version check is still required.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
CHANNEL_ID,
"Default",
NotificationManager.IMPORTANCE_DEFAULT);
channel.setDescription("Default push notifications");
NotificationManager nm = context.getSystemService(NotificationManager.class);
if (nm != null) {
nm.createNotificationChannel(channel);
}
}
}
}class PushHandler(context: Context) : PushMessageListener {
private val appContext = context.applicationContext
override fun onMessageReceived(message: RemoteMessage) {
val notification = message.notification ?: return // data-only payload
Log.i("ComapiPush", "Push notification: ${notification.body}")
createNotificationChannel()
val builder = NotificationCompat.Builder(appContext, CHANNEL_ID)
.setAutoCancel(true)
.setDefaults(NotificationCompat.DEFAULT_ALL)
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(notification.title)
.setContentText(notification.body)
if (ActivityCompat.checkSelfPermission(appContext, Manifest.permission.POST_NOTIFICATIONS)
== PackageManager.PERMISSION_GRANTED
) {
NotificationManagerCompat.from(appContext).notify(1, builder.build())
}
}
private fun createNotificationChannel() {
// Notification channels were introduced in API 26. The SDK's minSdk is 16,
// so the version check is still required.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID,
"Default",
NotificationManager.IMPORTANCE_DEFAULT
).apply { description = "Default push notifications" }
appContext.getSystemService(NotificationManager::class.java)
?.createNotificationChannel(channel)
}
}
companion object {
private const val CHANNEL_ID = "comapi_default_channel"
}
}Pass your class to pushMessageListener() on the ComapiConfig object:
config.pushMessageListener(new PushHandler(getApplicationContext()));config.pushMessageListener(PushHandler(applicationContext))Requesting the runtime notification permission (Android 13+)
private final ActivityResultLauncher<String> requestNotificationPermission =
registerForActivityResult(new ActivityResultContracts.RequestPermission(), granted -> {
// granted == true means the user accepted the prompt
});
private void ensureNotificationPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
&& ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
!= PackageManager.PERMISSION_GRANTED) {
requestNotificationPermission.launch(Manifest.permission.POST_NOTIFICATIONS);
}
}private val requestNotificationPermission =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { /* granted */ }
private fun ensureNotificationPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
!= PackageManager.PERMISSION_GRANTED
) {
requestNotificationPermission.launch(Manifest.permission.POST_NOTIFICATIONS)
}
}Reading the data when the user taps a system-tray notification
When the app is in the background and a notification payload is shown in the system tray, FCM doesn't deliver the message to PushMessageListener — it opens the launcher Activity and places the data fields in the Activity intent's extras. The SDK exposes a helper, handlePushNotification(...), which reads those extras, optionally records a click for analytics, and optionally launches the deep link.
Call it from Activity.onCreate(...) (and onNewIntent(...) if the Activity uses singleTop/singleTask) on the intent supplied by the system.
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
handleIfFromPush(getIntent());
}
@Override
protected void onNewIntent(@NonNull Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
handleIfFromPush(intent);
}
private void handleIfFromPush(@NonNull Intent intent) {
ComapiClient client = /* the client instance returned at init */;
client.handlePushNotification(this, intent, /* startActivity = */ true, result -> {
if (result == null) return;
String deepLinkUrl = result.getUrl(); // null if the payload had no deep link
JSONObject customData = result.getData(); // null if the payload had no `dd_data`
boolean clickTracked = result.isClickRecorded();
boolean deepLinkLaunched = result.isDeepLinkCalled();
// Update UI / route within the app as appropriate
});
}override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
handleIfFromPush(intent)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent)
handleIfFromPush(intent)
}
private fun handleIfFromPush(intent: Intent) {
val client: ComapiClient = /* the client instance returned at init */
client.handlePushNotification(this, intent, /* startActivity = */ true) { result ->
result ?: return@handlePushNotification
val deepLinkUrl: String? = result.url // null if no deep link
val customData: JSONObject? = result.data // null if no `dd_data`
val clickTracked: Boolean = result.isClickRecorded
val deepLinkLaunched: Boolean = result.isDeepLinkCalled
// Update UI / route within the app as appropriate
}
}
WhathandlePushNotificationdoesThe helper checks the launcher intent for the SDK's two well-known extras —
dd_deepLink(containing aurland optionaltrackingUrl) anddd_data. If a deep link is found andstartActivityistrue, it fires anACTION_VIEWintent for that URL. If atrackingUrlis present it also records the click against the Dotdigital analytics endpoint.
Parsing a RemoteMessage directly
If you receive the message in the foreground via PushMessageListener and want the same deep-link / custom-data extraction without firing any intents, use the static helper:
try {
PushDetails details = ComapiClient.parsePushMessage(message);
String deepLinkUrl = details.getUrl();
JSONObject customData = details.getData();
} catch (JSONException e) {
// payload was malformed
}try {
val details = ComapiClient.parsePushMessage(message)
val deepLinkUrl: String? = details.url
val customData: JSONObject? = details.data
} catch (e: JSONException) {
// payload was malformed
}