cometchat-android-v5-push — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited cometchat-android-v5-push (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Companion skills:cometchat-android-v5-corecovers init and login;cometchat-android-v5-productioncovers production auth and token security.
Push notifications are non-negotiable for production chat. Without them, a backgrounded app never wakes when a message arrives. This skill covers end-to-end FCM setup for CometChat Android v5 — using the correct CometChatNotifications API with PushPlatforms.FCM_ANDROID, notification channels, foreground/background handling, reply-from-notification, and tap-to-deep-link.
Ground truth: sample-app-java+push-notification/src/main/java/com/cometchat/sampleapp/java/fcm/ and sample-app-kotlin+push-notification/src/main/java/com/cometchat/sampleapp/kotlin/fcm/ in the v5 UIKit repository. Official docs: https://www.cometchat.com/docs/notifications/overview · Docs MCP: claude mcp add --transport http cometchat-docs https://www.cometchat.com/docs/mcp (or fetch the URL directly without MCP).
cometchat-android-v5-corecometchat-android-v5-troubleshootingcometchat-android-v5-featuresFCM (Google) → CometChat Dashboard → CometChat Server → Android ClientWhen user A sends a message to user B:
CometChatNotifications.registerPushToken())FirebaseMessagingService.onMessageReceived()All five steps must work. A broken step is almost always silent — no log, no error, just no notification.
applicationIdgoogle-services.json → place at app/google-services.json// project-level build.gradle
buildscript {
dependencies {
classpath 'com.google.gms:google-services:4.4.2'
}
}
// app-level build.gradle
apply plugin: 'com.google.gms.google-services'
dependencies {
implementation 'com.google.firebase:firebase-messaging:24.+'
}.json file.json file from §2c"Android-CometChat-Team-Messenger")You'll use this Provider ID in your client code when calling CometChatNotifications.registerPushToken().
CometChatNotificationsThe v5 SDK uses CometChatNotifications.registerPushToken() — not the deprecated CometChat.registerTokenForPushNotification().
| Method | Signature | Description |
|---|---|---|
registerPushToken | CometChatNotifications.registerPushToken(String token, PushPlatforms platform, String providerId, CallbackListener<String>) | Register FCM token with CometChat (param 2 is the PushPlatforms enum, e.g. PushPlatforms.FCM_ANDROID — not a String) |
unregisterPushToken | CometChatNotifications.unregisterPushToken(CallbackListener<String>) | Unregister token (call before logout) |
| Constant | Value | Use for |
|---|---|---|
PushPlatforms.FCM_ANDROID | "fcm_android" | Android FCM push |
Java:
public static void registerFCMToken(CometChat.CallbackListener<String> listener) {
FirebaseMessaging.getInstance().getToken().addOnCompleteListener(task -> {
if (task.isSuccessful()) {
String pushToken = task.getResult();
CometChatNotifications.registerPushToken(
pushToken,
PushPlatforms.FCM_ANDROID,
"YOUR_PROVIDER_ID", // from CometChat dashboard §3
new CometChat.CallbackListener<String>() {
@Override
public void onSuccess(String s) {
listener.onSuccess(s);
}
@Override
public void onError(CometChatException e) {
listener.onError(e);
}
}
);
} else {
listener.onError(new CometChatException("ERROR", "Failed to get FCM token"));
}
});
}Kotlin:
fun registerFCMToken(listener: CometChat.CallbackListener<String>) {
FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
if (task.isSuccessful) {
val pushToken = task.result
CometChatNotifications.registerPushToken(
pushToken,
PushPlatforms.FCM_ANDROID,
"YOUR_PROVIDER_ID", // from CometChat dashboard §3
object : CometChat.CallbackListener<String?>() {
override fun onSuccess(s: String?) {
listener.onSuccess(s)
}
override fun onError(e: CometChatException) {
listener.onError(e)
}
}
)
} else {
listener.onError(CometChatException("ERROR", "Failed to get FCM token"))
}
}
}Java:
public static void unregisterFCMToken(CometChat.CallbackListener<String> listener) {
CometChatNotifications.unregisterPushToken(new CometChat.CallbackListener<String>() {
@Override
public void onSuccess(String s) {
listener.onSuccess(s);
}
@Override
public void onError(CometChatException e) {
listener.onError(e);
}
});
}Kotlin:
fun unregisterFCMToken(listener: CometChat.CallbackListener<String>) {
CometChatNotifications.unregisterPushToken(object : CometChat.CallbackListener<String?>() {
override fun onSuccess(s: String?) {
listener.onSuccess(s)
}
override fun onError(e: CometChatException) {
listener.onError(e)
}
})
}Java:
public class FCMService extends FirebaseMessagingService {
private static String fcmToken;
@Override
public void onNewToken(@NonNull String token) {
super.onNewToken(token);
fcmToken = token;
// Re-register if user is already logged in
// (token rotation can happen at any time)
}
@Override
public void onMessageReceived(@NonNull RemoteMessage message) {
super.onMessageReceived(message);
if (message.getData().isEmpty()) return;
String type = message.getData().get("type");
if ("chat".equalsIgnoreCase(type)) {
handleChatMessage(message);
} else if ("call".equalsIgnoreCase(type)) {
handleCallMessage(message);
}
}
private void handleChatMessage(RemoteMessage message) {
FCMMessageDTO dto = new Gson().fromJson(
new Gson().toJson(message.getData()), FCMMessageDTO.class);
// Mark as delivered
CometChat.markAsDelivered(
Long.parseLong(dto.getTag()),
dto.getSender(),
dto.getReceiverType(),
dto.getReceiver()
);
// Build and show notification
Intent clickIntent = new Intent(this, SplashActivity.class);
FCMMessageNotificationUtils.showNotification(
this, dto, clickIntent,
"Reply", NotificationCompat.CATEGORY_MESSAGE
);
}
}Kotlin:
class FCMService : FirebaseMessagingService() {
companion object {
var fcmToken: String? = null
private set
}
override fun onNewToken(token: String) {
super.onNewToken(token)
fcmToken = token
}
override fun onMessageReceived(message: RemoteMessage) {
super.onMessageReceived(message)
if (message.data.isEmpty()) return
when (message.data["type"]?.lowercase()) {
"chat" -> handleChatMessage(message)
"call" -> handleCallMessage(message)
}
}
private fun handleChatMessage(message: RemoteMessage) {
val dto = Gson().fromJson(
Gson().toJson(message.data), FCMMessageDTO::class.java)
CometChat.markAsDelivered(
dto.tag!!.toLong(),
dto.sender!!,
dto.receiverType!!,
dto.receiver!!
)
val clickIntent = Intent(this, SplashActivity::class.java)
FCMMessageNotificationUtils.showNotification(
this, dto, clickIntent,
"Reply", NotificationCompat.CATEGORY_MESSAGE
)
}
}<service
android:name=".fcm.FCMService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>CometChat sends data-only FCM messages. The payload fields:
type: "chat")| Field | Type | Description |
|---|---|---|
type | String | Always "chat" for messages |
sender | String | Sender UID |
senderName | String | Sender display name |
senderAvatar | String | Sender avatar URL |
receiver | String | Receiver UID (user) or GUID (group) |
receiverName | String | Receiver display name |
receiverType | String | "user" or "group" |
receiverAvatar | String | Receiver avatar URL |
conversationId | String | Conversation ID |
body | String | Message text |
title | String | Notification title |
tag | String | Message ID (as string) |
unreadMessageCount | String | Unread count (as string) |
type: "call")| Field | Type | Description |
|---|---|---|
type | String | Always "call" |
callAction | String | "initiated", "cancelled", "unanswered" |
sessionId | String | Call session ID |
callType | String | "audio" or "video" |
sender / receiver / senderName / etc. | String | Same as chat payload |
Java:
public class FCMMessageDTO {
@SerializedName("conversationId") private String conversationId;
@SerializedName("sender") private String sender;
@SerializedName("receiver") private String receiver;
@SerializedName("receiverName") private String receiverName;
@SerializedName("receiverType") private String receiverType;
@SerializedName("receiverAvatar") private String receiverAvatar;
@SerializedName("tag") private String tag;
@SerializedName("body") private String text;
@SerializedName("type") private String type;
@SerializedName("title") private String title;
@SerializedName("senderAvatar") private String senderAvatar;
@SerializedName("senderName") private String senderName;
@SerializedName("unreadMessageCount") private String unreadMessageCount;
// getters and setters
}Kotlin:
class FCMMessageDTO {
@SerializedName("conversationId") var conversationId: String? = null
@SerializedName("sender") var sender: String? = null
@SerializedName("receiver") var receiver: String? = null
@SerializedName("receiverName") var receiverName: String? = null
@SerializedName("receiverType") var receiverType: String? = null
@SerializedName("receiverAvatar") var receiverAvatar: String? = null
@SerializedName("tag") var tag: String? = null
@SerializedName("body") var text: String? = null
@SerializedName("type") var type: String? = null
@SerializedName("title") var title: String? = null
@SerializedName("senderAvatar") var senderAvatar: String? = null
@SerializedName("senderName") var senderName: String? = null
@SerializedName("unreadMessageCount") var unreadMessageCount: String? = null
}Parse from RemoteMessage:
FCMMessageDTO dto = new Gson().fromJson(new Gson().toJson(message.getData()), FCMMessageDTO.class);Create channels in your Application.onCreate() or before showing the first notification:
Java:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationManager manager = getSystemService(NotificationManager.class);
NotificationChannel messageChannel = new NotificationChannel(
"Message", // channel ID
"Message notification", // channel name
NotificationManager.IMPORTANCE_HIGH
);
manager.createNotificationChannel(messageChannel);
NotificationChannel callChannel = new NotificationChannel(
"Call",
"Call notification",
NotificationManager.IMPORTANCE_HIGH
);
callChannel.setSound(null, null); // calls use their own ringtone
manager.createNotificationChannel(callChannel);
}Kotlin:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val manager = getSystemService(NotificationManager::class.java)
val messageChannel = NotificationChannel(
"Message", "Message notification", NotificationManager.IMPORTANCE_HIGH
)
manager.createNotificationChannel(messageChannel)
val callChannel = NotificationChannel(
"Call", "Call notification", NotificationManager.IMPORTANCE_HIGH
).apply { setSound(null, null) }
manager.createNotificationChannel(callChannel)
}The notification click intent routes through SplashActivity → HomeActivity → correct fragment/conversation.
Java:
Intent clickIntent = new Intent(context, SplashActivity.class);
clickIntent.putExtra("NOTIFICATION_TYPE", "NOTIFICATION_TYPE_MESSAGE");
clickIntent.putExtra("NOTIFICATION_DATA", new Gson().toJson(fcmMessageDTO));
clickIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(
context, notificationId, clickIntent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
);// In HomeActivity.onCreate() or onNewIntent()
String notificationType = getIntent().getStringExtra("NOTIFICATION_TYPE");
String notificationPayload = getIntent().getStringExtra("NOTIFICATION_DATA");
if ("NOTIFICATION_TYPE_MESSAGE".equals(notificationType) && notificationPayload != null) {
FCMMessageDTO dto = new Gson().fromJson(notificationPayload, FCMMessageDTO.class);
if ("chat".equalsIgnoreCase(dto.getType())) {
// Navigate to the conversation
boolean isUser = "user".equals(dto.getReceiverType());
String uid = isUser ? dto.getSender() : dto.getReceiver();
// Open MessagesActivity with uid/guid
}
}The sample apps support inline reply from the notification tray using RemoteInput:
Java:
RemoteInput remoteInput = new RemoteInput.Builder("key_text_reply")
.setLabel("Reply")
.build();
NotificationCompat.Action replyAction = new NotificationCompat.Action.Builder(
R.drawable.ic_reply, "Reply", replyPendingIntent)
.addRemoteInput(remoteInput)
.build();
builder.addAction(replyAction);The reply is received in a BroadcastReceiver:
public class FCMMessageBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
if (remoteInput != null) {
CharSequence replyText = remoteInput.getCharSequence("key_text_reply");
// Send as TextMessage via CometChat.sendMessage()
}
}
}<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.INTERNET" />Request POST_NOTIFICATIONS at runtime on Android 13+:
Java:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, 100);
}
}Kotlin:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(arrayOf(Manifest.permission.POST_NOTIFICATIONS), 100)
}
}CometChat sends unreadMessageCount in the payload. Apply it:
String unreadCountStr = message.getData().get("unreadMessageCount");
if (unreadCountStr != null) {
int count = Integer.parseInt(unreadCountStr);
ShortcutBadger.applyCount(getApplicationContext(), count);
}Clear on app open:
ShortcutBadger.removeCount(this);| Step | How | What it verifies |
|---|---|---|
| 1. FCM alone | Firebase Console → Cloud Messaging → Send test message to your FCM token | Firebase + google-services.json are correct |
| 2. CometChat → device | Send a message to the logged-in user from another user (dashboard or another device) | Dashboard provider config + token registration |
| 3. Tap deep-link | Background the app, send a message, tap the notification | PendingIntent routing to correct conversation |
| 4. Reply from notification | Pull down notification, type reply, send | BroadcastReceiver + CometChat.sendMessage() |
| 5. Token rotation | Clear app data, re-open → onNewToken() fires | onNewToken() re-registers with CometChat |
| Symptom | Likely cause | Fix |
|---|---|---|
| Token prints but no push arrives | Token registered BEFORE login, or wrong Provider ID | Call registerPushToken() AFTER CometChatUIKit.login() resolves. Verify Provider ID matches dashboard. |
| Foreground: nothing shows | No onMessageReceived() implementation or no notification channel | Implement FCMService.onMessageReceived() + create NotificationChannel on API 26+ |
| "Default FirebaseApp is not initialized" | google-services.json missing or Gradle plugin not applied | Re-check §2. Clean build: ./gradlew clean |
| Notification tap doesn't navigate | PendingIntent missing extras or wrong Activity | Pass NOTIFICATION_TYPE + NOTIFICATION_DATA in Intent extras, handle in target Activity |
| Works for User A but not User B after logout | unregisterPushToken not called on logout | Call CometChatNotifications.unregisterPushToken() BEFORE CometChatUIKit.logout() |
| No notifications on Android 13+ | Missing POST_NOTIFICATIONS runtime permission | Request at runtime before registering token |
Using deprecated registerTokenForPushNotification() | Old API from v4 | Use CometChatNotifications.registerPushToken() with PushPlatforms.FCM_ANDROID |
| Provider ID mismatch | Client uses different Provider ID than dashboard | Copy exact Provider ID string from CometChat dashboard → Notifications → Push |
Call pushes (type: "call") require VoIP permissions and CometChatVoIP integration. The flow:
onMessageReceived() detects type == "call"callAction: "initiated" → show incoming call, "cancelled" / "unanswered" → dismissCometChatVoIP.hasReadPhoneStatePermission(), hasManageOwnCallsPermission(), hasAnswerPhoneCallsPermission()CometChatVoIP.addNewIncomingCall() with call details in a BundleThis is an advanced topic — see the voip/ package in the sample apps for the full implementation.
PushPlatforms.FCM_ANDROID and a Provider ID.CometChatNotifications.unregisterPushToken() before CometChatUIKit.logout().~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.