cometchat-android-v5-customization — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited cometchat-android-v5-customization (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.
Ground truth:com.cometchat:chat-uikit-android:5.x(legacy/maintenance-only; +calls-sdk-android:5.x) — resolved AAR (javap) +ui-kit/android. Official docs: https://www.cometchat.com/docs/ui-kit/android/overview · Docs MCP:claude mcp add --transport http cometchat-docs https://www.cometchat.com/docs/mcp(or fetch the URL directly without MCP). Verify symbols against the installed package/source before relying on them.
Companion skills:cometchat-android-v5-componentsprovides the component catalog;cometchat-android-v5-themingcovers visual styling;cometchat-android-v5-extensionscovers the extension architecture.
This skill teaches how to customize CometChat components beyond what theming provides. It covers custom message templates, the DataSource/DataSourceDecorator pattern, ChatConfigurator, event listeners, and custom view slots.
cometchat-android-v5-themingcometchat-android-v5-featurescometchat-android-v5-coreTemplates define how message bubbles are rendered. Each template maps a message type + category to custom views.
Prefer the decorator (§3) for anything app-wide. The per-listsetTemplates(...)shown here only affects the oneCometChatMessageListyou call it on. To register a custom type, override a built-in bubble, add an attachment, or add a long-press option everywhere, use the fourDataSourceDecoratorrecipes in §3 — that is the pattern the kit's own extensions use.
Java:
CometChatMessageTemplate template = new CometChatMessageTemplate()
.setType(CometChatConstants.MESSAGE_TYPE_TEXT)
.setCategory(CometChatConstants.CATEGORY_MESSAGE)
.setContentView(new MessagesViewHolderListener() {
@Override
public View createView(Context context, CometChatMessageBubble messageBubble,
UIKitConstants.MessageBubbleAlignment alignment) {
// Return your custom view
return LayoutInflater.from(context).inflate(R.layout.custom_text_bubble, null);
}
@Override
public void bindView(Context context, View createdView, BaseMessage message,
UIKitConstants.MessageBubbleAlignment alignment,
RecyclerView.ViewHolder holder, List<BaseMessage> messageList,
int position) {
// Bind data to your custom view
TextView textView = createdView.findViewById(R.id.customText);
textView.setText(((TextMessage) message).getText());
}
})
.setOptions((context, baseMessage, group) -> {
// Return custom message options (long-press menu)
List<CometChatMessageOption> options = new ArrayList<>();
options.add(new CometChatMessageOption("custom_action", "Custom Action",
R.drawable.ic_custom, () -> { /* handle click */ }));
return options;
});
// Apply to message list — APPEND to defaults, never replace.
// ❌ messageList.setTemplates(Collections.singletonList(template)); // wipes ALL default bubbles
List<CometChatMessageTemplate> templates =
CometChatUIKit.getDataSource().getMessageTemplates(new AdditionParameter());
templates.add(template); // keep text/image/video/file/… + add yours
messageList.setTemplates(templates);The #1 custom-bubble mistake:setTemplates(singletonList(myTemplate))replaces the entire template set, so every default bubble (text, image, video, audio, file, …) stops rendering. Always seed the list fromCometChatUIKit.getDataSource().getMessageTemplates(new AdditionParameter())andadd()your template — or register aDataSourceDecorator(§3) so the append applies app-wide.
Kotlin:
val template = CometChatMessageTemplate()
.setType(CometChatConstants.MESSAGE_TYPE_TEXT)
.setCategory(CometChatConstants.CATEGORY_MESSAGE)
.setContentView(object : MessagesViewHolderListener() {
override fun createView(context: Context, messageBubble: CometChatMessageBubble,
alignment: UIKitConstants.MessageBubbleAlignment): View {
return LayoutInflater.from(context).inflate(R.layout.custom_text_bubble, null)
}
override fun bindView(context: Context, createdView: View, message: BaseMessage,
alignment: UIKitConstants.MessageBubbleAlignment,
holder: RecyclerView.ViewHolder, messageList: List<BaseMessage>,
position: Int) {
val textView = createdView.findViewById<TextView>(R.id.customText)
textView.text = (message as TextMessage).text
}
})
.setOptions { context, baseMessage, group ->
listOf(CometChatMessageOption("custom_action", "Custom Action",
R.drawable.ic_custom) { /* handle click */ })
}
// APPEND to defaults — don't replace.
// ❌ messageList.setTemplates(listOf(template)) // wipes ALL default bubbles
val templates = CometChatUIKit.getDataSource().getMessageTemplates(AdditionParameter())
templates.add(template)
messageList.setTemplates(templates)| Slot | Method | Description |
|---|---|---|
bubbleView | setBubbleView(MessagesViewHolderListener) | Entire message bubble |
headerView | setHeaderView(MessagesViewHolderListener) | Above the bubble |
contentView | setContentView(MessagesViewHolderListener) | Inside the bubble |
bottomView | setBottomView(MessagesViewHolderListener) | Below the content, inside bubble |
footerView | setFooterView(MessagesViewHolderListener) | Below the bubble |
statusInfoView | setStatusInfoView(MessagesViewHolderListener) | Status info area |
replyView | setReplyView(MessagesViewHolderListener) | Reply preview above bubble |
Custom actions in the message long-press menu. CometChatMessageOption extends CometChatOption. Verified constructor (CometChatMessageOption.java:43):
public CometChatMessageOption(@NotNull String id, @NotNull String title, @ColorInt int icon, OnClick onClick)Java:
CometChatMessageOption option = new CometChatMessageOption(
"bookmark", // unique ID
"Bookmark", // title
R.drawable.ic_bookmark, // icon — see note below
() -> { // onClick (com.cometchat.chatuikit.shared.interfaces.OnClick)
// Handle bookmark action
}
);Icon-param quirk: the 4-arg constructor's third param is annotated@ColorInt int icon, but the kit's own decorators pass a drawable resource here (e.g.MessageTranslationDecorator.java:53passesR.drawable.cometchat_ic_translate). Pass yourR.drawable.*id — the annotation is misleading, the runtime treats it as a drawable. The 7-arg constructor (CometChatMessageOption.java:58) addstitleColor,iconTintColor,titleAppearanceif you need styling.
The DataSource interface defines how messages are rendered and what options are available. DataSourceDecorator wraps an existing DataSource to add or modify behavior without replacing it.
Java:
public class CustomDataSource extends DataSourceDecorator {
public CustomDataSource(DataSource dataSource) {
super(dataSource);
}
@Override
public List<CometChatMessageOption> getTextMessageOptions(Context context,
BaseMessage baseMessage, Group group, AdditionParameter additionParameter) {
List<CometChatMessageOption> options = super.getTextMessageOptions(context,
baseMessage, group, additionParameter);
// Add custom option
options.add(new CometChatMessageOption("translate", "Translate",
R.drawable.ic_translate, () -> { /* translate */ }));
return options;
}
}Kotlin:
class CustomDataSource(dataSource: DataSource) : DataSourceDecorator(dataSource) {
override fun getTextMessageOptions(context: Context, baseMessage: BaseMessage,
group: Group?, additionParameter: AdditionParameter): List<CometChatMessageOption> {
val options = super.getTextMessageOptions(context, baseMessage, group, additionParameter).toMutableList()
options.add(CometChatMessageOption("translate", "Translate",
R.drawable.ic_translate) { /* translate */ })
return options
}
}Java:
ChatConfigurator.enable(dataSource -> new CustomDataSource(dataSource));Kotlin:
ChatConfigurator.enable { dataSource -> CustomDataSource(dataSource) }The canonical Android V5 extension surface is the DataSource chain. The four most common requests each map to one overridden method on your DataSourceDecorator. Every override *must call `super.` first** and append — returning a fresh list/template wipes the kit's defaults. These are the exact methods + signatures the kit's own extensions use; cited file:line for each.
#### Scenario 1 — Register a custom message type (new bubble)
Override getMessageTemplates(...) and append a CometChatMessageTemplate whose setCategory(...) is UIKitConstants.MessageCategory.CUSTOM and whose setType(...) is your custom type string. Render its bubble with setContentView(MessagesViewHolderListener). This mirrors PollsExtensionDecorator.getMessageTemplates (PollsExtensionDecorator.java:82-86) + getPollTemplate (PollsExtensionDecorator.java:220-309).
Java:
@Override
public List<CometChatMessageTemplate> getMessageTemplates(@NonNull AdditionParameter additionParameter) {
List<CometChatMessageTemplate> templates = super.getMessageTemplates(additionParameter); // keep defaults
templates.add(getLocationTemplate(additionParameter));
return templates;
}
private CometChatMessageTemplate getLocationTemplate(@NonNull AdditionParameter additionParameter) {
return new CometChatMessageTemplate()
.setCategory(UIKitConstants.MessageCategory.CUSTOM) // CUSTOM, not MESSAGE
.setType("location") // your custom type
// setOptions: Function3<Context, BaseMessage, Group, List<CometChatMessageOption>>
.setOptions((context, baseMessage, group) ->
ChatConfigurator.getDataSource().getCommonOptions(context, baseMessage, group, additionParameter))
.setContentView(new MessagesViewHolderListener() {
@Override
public View createView(Context context, CometChatMessageBubble messageBubble,
UIKitConstants.MessageBubbleAlignment alignment) {
return LayoutInflater.from(context).inflate(R.layout.custom_location_bubble, null);
}
@Override
public void bindView(Context context, View createdView, BaseMessage message,
UIKitConstants.MessageBubbleAlignment alignment,
RecyclerView.ViewHolder holder, List<BaseMessage> messageList, int position) {
// bind from (CustomMessage) message customData
}
});
}You usually also want the type to count as a known type: overridegetDefaultMessageTypes(...)/getDefaultMessageCategories(...)and append your type /MessageCategory.CUSTOM(Polls does exactly this —PollsExtensionDecorator.java:171-186).
#### Scenario 2 — Override an existing built-in type's bubble
Do not rebuild the template list. Override the per-type template getter and mutate the template returned by super. The verified getters on DataSource are getTextTemplate(AdditionParameter), getImageTemplate, getVideoTemplate, getAudioTemplate, getFileTemplate (DataSource.java:230-244), plus the lookup getMessageTemplate(String category, String type, AdditionParameter) (DataSource.java:260). The decorator forwards each to the wrapped source (DataSourceDecorator.java:598, :652), so call super then re-skin only the slot you want.
Java:
@Override
public CometChatMessageTemplate getTextTemplate(@NonNull AdditionParameter additionParameter) {
CometChatMessageTemplate template = super.getTextTemplate(additionParameter); // keeps type/category/options
template.setContentView(new MessagesViewHolderListener() { // swap only the content slot
@Override
public View createView(Context context, CometChatMessageBubble messageBubble,
UIKitConstants.MessageBubbleAlignment alignment) {
return LayoutInflater.from(context).inflate(R.layout.my_text_bubble, null);
}
@Override
public void bindView(Context context, View createdView, BaseMessage message,
UIKitConstants.MessageBubbleAlignment alignment,
RecyclerView.ViewHolder holder, List<BaseMessage> messageList, int position) {
((TextView) createdView.findViewById(R.id.body)).setText(((TextMessage) message).getText());
}
});
return template;
}setContentView/setBubbleView/setHeaderView/setBottomView/setStatusInfoView/setFooterView/setReplyViewall take aMessagesViewHolderListenerand return the template (CometChatMessageTemplate.java:79-202) — pick the slot you need. OverridinggetTextTemplatereskins every text bubble app-wide while preserving its default options/category/type.
#### Scenario 3 — Add a composer attachment option
Override getAttachmentOptions(...), call super, and append a CometChatMessageComposerAction (a builder — setId/setTitle/setIcon/setOnClick, CometChatMessageComposerAction.java:46-127). This is exactly PollsExtensionDecorator.getAttachmentOptions (PollsExtensionDecorator.java:88-168).
Java:
@Override
public List<CometChatMessageComposerAction> getAttachmentOptions(Context context, User user,
Group group, HashMap<String, String> idMap, @NonNull AdditionParameter additionParameter) {
List<CometChatMessageComposerAction> options =
super.getAttachmentOptions(context, user, group, idMap, additionParameter); // keep camera/gallery/file/poll/…
options.add(new CometChatMessageComposerAction()
.setId("location")
.setTitle("Send Location")
.setIcon(R.drawable.ic_location)
.setOnClick(() -> { /* pick + send (see below) */ }));
return options;
}#### Scenario 4 — Add a message long-press option (Forward-style)
Override the per-type option getter — getTextMessageOptions, getImageMessageOptions, getVideoMessageOptions, getAudioMessageOptions, or getFileMessageOptions (DataSource.java:49-108) — call super, then append a CometChatMessageOption. This is exactly MessageTranslationDecorator.getTextMessageOptions (MessageTranslationDecorator.java:44-59).
Java:
@Override
public List<CometChatMessageOption> getTextMessageOptions(Context context,
BaseMessage baseMessage, Group group, @NonNull AdditionParameter additionParameter) {
List<CometChatMessageOption> options =
super.getTextMessageOptions(context, baseMessage, group, additionParameter); // keep reply/copy/edit/delete/…
if (baseMessage != null && baseMessage.getDeletedAt() == 0) {
options.add(new CometChatMessageOption(
"forward", "Forward", R.drawable.ic_forward, () -> { /* forward baseMessage */ }));
}
return options;
}getMessageOptions(...)(DataSource.java:271) is the aggregate dispatcher the kit calls per message; it routes to the per-type getters above. To add an option to one message type, override that type's getter (as above). OverridegetMessageOptionsonly when you want the option on every message type at once.
Append rule (all four): an override that returns a freshnew ArrayList<>()instead of starting fromsuper.*wipes every default (bubbles, attachments, or options). Always seed fromsuper.
Register the decorator app-wide via ChatConfigurator.enable(...) (§"Register via ChatConfigurator" above) — it accepts Function1<DataSource, DataSource> (ChatConfigurator.java:39). For a packaged two-class extension (registrar + decorator), see cometchat-android-v5-extensions.
CustomMessage message = new CustomMessage(
receiverId, CometChatConstants.RECEIVER_TYPE_USER, // or RECEIVER_TYPE_GROUP
"location", // matches the template type
customData); // JSONObject
// ✅ kit path — sets muid + sender, fires ccMessageSent → list APPENDS
CometChatUIKit.sendCustomMessage(message, new CometChat.CallbackListener<CustomMessage>() {
@Override public void onSuccess(CustomMessage m) { }
@Override public void onError(CometChatException e) { }
});
// ❌ CometChat.sendCustomMessage(...) — no muid/sender/event → recipient error + realtime replaceVerified against kit source (chat-uikit-android 5.x):
CometChatUIKit.getDataSource(), ChatConfigurator.getDataSource() / ChatConfigurator.enable(Function1<DataSource,DataSource>) (ChatConfigurator.java:39,55)DataSource.getMessageTemplates(AdditionParameter) (DataSource.java:248); per-type template getters getTextTemplate/getImageTemplate/getVideoTemplate/getAudioTemplate/getFileTemplate(AdditionParameter) (DataSource.java:230-244); lookup getMessageTemplate(String category, String type, AdditionParameter) (DataSource.java:260)getTextMessageOptions/getImageMessageOptions/getVideoMessageOptions/getAudioMessageOptions/getFileMessageOptions(Context, BaseMessage, Group, AdditionParameter) (DataSource.java:49-108); aggregate getMessageOptions(...) (DataSource.java:271)DataSource.getAttachmentOptions(Context, User, Group, HashMap, AdditionParameter) (DataSource.java:286)CometChatMessageTemplate setters setType/setCategory(String), setOptions(Function3<Context,BaseMessage,Group,List<CometChatMessageOption>>), setContentView/setBubbleView/setHeaderView/setBottomView/setStatusInfoView/setFooterView/setReplyView(MessagesViewHolderListener) (CometChatMessageTemplate.java:79-237)CometChatMessageComposerAction builder setId/setTitle/setIcon/setOnClick(...) (CometChatMessageComposerAction.java:46-127); CometChatMessageOption(String,String,int,OnClick) (CometChatMessageOption.java:43)MessagesViewHolderListener.createView(...) + bindView(...) (MessagesViewHolderListener.java:18-26)CometChatUIKit.sendCustomMessage(CustomMessage, CallbackListener)PollsExtensionDecorator.java (template + attachment), MessageTranslationDecorator.java (message option)CometChat provides event classes for reacting to chat events. Register listeners with a unique tag.
Java:
CometChatMessageEvents.addListener("unique-tag", new CometChatMessageEvents() {
@Override
public void ccMessageSent(BaseMessage baseMessage, int status) {
// Message sent
}
@Override
public void onTextMessageReceived(TextMessage textMessage) {
// Text message received
}
@Override
public void onMessageReactionAdded(ReactionEvent reactionEvent) {
// Reaction added
}
});
// Remove when done
CometChatMessageEvents.removeListener("unique-tag");Kotlin:
CometChatMessageEvents.addListener("unique-tag", object : CometChatMessageEvents() {
override fun ccMessageSent(baseMessage: BaseMessage, status: Int) {
// Message sent
}
override fun onTextMessageReceived(textMessage: TextMessage) {
// Text message received
}
override fun onMessageReactionAdded(reactionEvent: ReactionEvent) {
// Reaction added
}
})
// Remove when done
CometChatMessageEvents.removeListener("unique-tag")CometChatUserEvents.addUserListener("unique-tag", new CometChatUserEvents() {
@Override
public void ccUserBlocked(User user) { }
@Override
public void ccUserUnblocked(User user) { }
});CometChatGroupEvents.addGroupListener("unique-tag", new CometChatGroupEvents() {
@Override
public void ccGroupCreated(Group group) { }
@Override
public void ccGroupDeleted(Group group) { }
@Override
public void ccGroupMemberJoined(User joinedUser, Group joinedGroup) { }
@Override
public void ccGroupMemberKicked(Action action, User kicked, User kickedBy, Group from) { }
@Override
public void ccGroupMemberBanned(Action action, User banned, User bannedBy, Group from) { }
});CometChatCallEvents.addListener("unique-tag", new CometChatCallEvents() {
@Override
public void ccOutgoingCall(Call call) { }
@Override
public void ccCallAccepted(Call call) { }
@Override
public void ccCallRejected(Call call) { }
@Override
public void ccCallEnded(Call call) { }
});Most components support custom view injection via Function3<Context, User, Group, View>:
CometChatMessageHeader header = findViewById(R.id.header);
header.setSubtitleView((context, user, group) -> {
TextView subtitle = new TextView(context);
if (user != null) {
subtitle.setText(user.getStatus().equals("online") ? "Active now" : "Offline");
}
return subtitle;
});onDestroy() or onDestroyView().createView() and bindView().The V5 UI Kit surfaces CometChatSoundManager for incoming/outgoing message + call sounds (mute / custom audio). V5 is legacy/maintenance-only — V6 is the current cohort. For call sounds see this family's -calls skill; verify the CometChatSoundManager API against the installed kit before relying on it.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.