agentsclimarketplace

Cometchat android v6 compose customization

Skill cometchat/cometchat-skills/skills/cometchat-android-v6-compose-customization

CometChat Android UIKit v6 Compose customization — BubbleFactory interface, slot lambdas, @Immutable style classes, and per-slot overridesFrom its SKILL.md

Install
npx -y skills add cometchat/cometchat-skills --skill cometchat-android-v6-compose-customization

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.

What its file declares

Copied from the file, not written here

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

20.0 KB, ~4.6k tokens by cl100k_base, as published. Nobody here has run it

Ground truth: com.cometchat:chatuikit-{compose,kotlin}-android:6.x (+ calls-sdk-android:5.x) — resolved AAR (javap) + ui-kit/android/v6. Official docs: https://www.cometchat.com/docs/ui-kit/android/v6/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-v6-kotlin-customization (Views equivalent), cometchat-android-v6-compose-components, cometchat-android-v6-compose-theming, cometchat-android-v6-extensions (DataSource layer), cometchat-android-v6-events

Purpose

Customize CometChat Compose components — override message bubble rendering with BubbleFactory, use slot lambda parameters for per-slot overrides, and apply @Immutable style classes. This is the v6 replacement for v5's DataSource/ChatConfigurator pattern.

Use this skill when

  • Creating custom message bubble rendering for specific message types
  • Overriding individual bubble slots (avatar, header, footer, content, etc.)
  • Applying custom styles to components
  • Replacing the entire bubble layout for a message type

Do not use this skill when

  • Working with Kotlin Views customization (use cometchat-android-v6-kotlin-customization)
  • Changing theme colors globally (use cometchat-android-v6-compose-theming)
  • Extending the data layer (use cometchat-android-v6-extensions)

1. BubbleFactory (Message Bubble Customization)

BubbleFactory is an interface in com.cometchat.uikit.compose.presentation.shared.messagebubble. Each implementation handles a specific message type.

1.1 Creating a Custom BubbleFactory

import com.cometchat.uikit.compose.presentation.shared.messagebubble.BubbleFactory
import com.cometchat.chat.constants.CometChatConstants
import com.cometchat.chat.models.BaseMessage
import com.cometchat.uikit.core.constants.UIKitConstants.MessageBubbleAlignment

class LocationBubbleFactory : BubbleFactory {

    override fun getCategory(): String = CometChatConstants.CATEGORY_CUSTOM
    override fun getType(): String = "location"

    override fun getContentView(
        message: BaseMessage,
        alignment: MessageBubbleAlignment,
        style: CometChatMessageBubbleStyle,
        textFormatters: List<CometChatTextFormatter>
    ): @Composable () -> Unit = {
        // Your custom content composable
        val metadata = message.metadata
        val lat = metadata?.optDouble("latitude") ?: 0.0
        val lng = metadata?.optDouble("longitude") ?: 0.0
        LocationMapView(latitude = lat, longitude = lng)
    }
}

1.2 Registering BubbleFactories

CometChatMessageList(
    user = user,
    bubbleFactories = listOf(
        LocationBubbleFactory(),
        PaymentBubbleFactory()
    )
)

The list is converted to a map keyed by "category_type" internally via toFactoryMap().

1.3 Complete Bubble Replacement

Override getBubbleView() to replace the ENTIRE bubble (all slots):

class CustomBubbleFactory : BubbleFactory {
    override fun getCategory(): String = "custom"
    override fun getType(): String = "payment"

    override fun getBubbleView(
        message: BaseMessage,
        alignment: MessageBubbleAlignment
    ): (@Composable () -> Unit)? = {
        // Complete custom bubble — no header, footer, avatar, etc.
        PaymentCard(message = message, alignment = alignment)
    }
}

When getBubbleView() returns non-null, all other slot methods are ignored.

1.4 Slot Methods

All return (@Composable () -> Unit)? — return null to use defaults:

MethodSlotParameters
getContentView()Main contentmessage, alignment, style, textFormatters
getLeadingView()Avatarmessage, alignment, style
getHeaderView()Sender namemessage, alignment, style, showTime
getReplyView()Reply previewmessage, alignment, style
getBottomView()Moderationmessage, alignment, style, hideModerationView
getStatusInfoView()Timestamp/receiptsmessage, alignment, style, showTime
getThreadView()Thread indicatormessage, alignment, style, onThreadRepliesClick
getFooterView()Reactionsmessage, alignment, style, onReactionClick, onReactionLongClick, onAddMoreReactionsClick

1.5 Style Override

override fun getBubbleStyle(
    message: BaseMessage,
    alignment: MessageBubbleAlignment
): CometChatMessageBubbleStyle? {
    return CometChatMessageBubbleStyle(
        backgroundColor = Color(0xFFE8F5E9),
        cornerRadius = 16.dp
    )
}

Factory style is the highest priority in the 3-tier style chain.

1.6 Lifecycle

override fun onDispose(message: BaseMessage) {
    // Clean up resources when bubble leaves composition
}

1.7 Custom attachment options (Scenario 3) — pass the attachmentOptions parameter

A BubbleFactory only renders a custom type; to let users create one you usually add a composer attachment action. On Compose the canonical path is the attachmentOptions parameter on CometChatMessageComposer — a List<CometChatMessageComposerAction> whose contents are added after the built-in options.

CometChatMessageComposer(
    user = user,
    attachmentOptions = listOf(
        CometChatMessageComposerAction(
            id = "location",
            title = "Send Location",
            icon = R.drawable.ic_location,
            onClick = OnClick { /* pick + send (see 1.8) */ }
        )
    )
)

Append is automatic — your list does NOT replace the defaults. Internally the composable calls composerViewModel.setAttachmentOptions(attachmentOptions) (CometChatMessageComposer.kt:431-432), which sets only the custom options list (_customAttachmentOptions). The defaults (camera/image/video/audio/document/poll/collab-doc/collab-whiteboard) are rebuilt every time by getDefaultAttachmentOptions(...) and your custom options are concatenated at the end (CometChatMessageComposerViewModel.kt:646-649). There is no "wipe defaults" footgun here — setAttachmentOptions despite its name does NOT drop the built-ins.

Hide a built-in instead with the composer's hide*Option flags (e.g. hidePollOption = true), which flip the _show*Option visibility checked inside getDefaultAttachmentOptions.

If you hold a CometChatMessageComposerViewModel directly (advanced), addAttachmentOption(action) appends one custom option and setAttachmentOptions(list) replaces the custom list — both verified at CometChatMessageComposerViewModel.kt:657 / :666. The built-in option IDs are constants on CometChatMessageComposerAction (ID_CAMERA, ID_IMAGE, ID_VIDEO, ID_AUDIO, ID_DOCUMENT, ID_POLL, ID_COLLABORATIVE_DOCUMENT, ID_COLLABORATIVE_WHITEBOARD — CometChatMessageComposerAction.kt:42-49).

1.8 Sending a custom message — use the UIKit path, not the raw SDK

val message = CustomMessage(
    receiverId,
    CometChatConstants.RECEIVER_TYPE_USER,   // or RECEIVER_TYPE_GROUP
    "location",                              // matches the BubbleFactory type
    customData                               // org.json.JSONObject
)
// ✅ kit path — sets muid + sender, fires ccMessageSent → list APPENDS
CometChatUIKit.sendCustomMessage(message, object : CometChat.CallbackListener<CustomMessage>() {
    override fun onSuccess(p0: CustomMessage) {}
    override fun onError(p0: CometChatException) {}
})
// ❌ CometChat.sendCustomMessage(...) — no muid/sender/event → recipient error + realtime replace

Verified (v6 core/compose): CometChatMessageComposer(attachmentOptions = …) param (CometChatMessageComposer.kt:338), CometChatMessageComposerViewModel.addAttachmentOption / setAttachmentOptions / getDefaultAttachmentOptions (:657 / :666 / :547), CometChatUIKit.sendCustomMessage(CustomMessage, CallbackListener) (CometChatUIKit.kt:472).

1.9 Override an EXISTING type's bubble (Scenario 2)

To replace the rendering of a built-in type (e.g. text or image), register a BubbleFactory whose getCategory()/getType() match the built-in identifiers. Built-in types are NOT pre-seeded as factory entries — the bubble falls back to InternalContentRenderer only when no factory matches a "category_type" key (CometChatMessageBubble.kt:155 "Falls back to InternalContentRenderer if no factory is provided"). So a matching factory simply takes priority over the internal default.

import com.cometchat.chat.constants.CometChatConstants

class MyTextBubbleFactory : BubbleFactory {
    override fun getCategory(): String = CometChatConstants.CATEGORY_MESSAGE   // "message"
    override fun getType(): String = CometChatConstants.MESSAGE_TYPE_TEXT      // "text"

    override fun getContentView(
        message: BaseMessage,
        alignment: UIKitConstants.MessageBubbleAlignment,
        style: CometChatMessageBubbleStyle,
        textFormatters: List<CometChatTextFormatter>
    ): @Composable () -> Unit = {
        MyCustomTextContent(message as TextMessage)   // only the content area
    }
    // …or override getBubbleView() to replace the whole bubble (see 1.3)
}

CometChatMessageList(user = user, bubbleFactories = listOf(MyTextBubbleFactory()))

This is still additive at the list level: registering a message_text factory only overrides text bubbles; image/video/custom types keep their internal rendering. Use getContentView() for content-only override, getBubbleView() for full-bubble replacement.

1.10 Add a long-press message option (Scenario 4 — Forward-style)

CometChatMessageList exposes two lambda parameters for the long-press option sheet (CometChatMessageList.kt:577-598):

ParamTypeSemantics
addOptions((BaseMessage) -> List<CometChatMessageOption>)?Append after the defaults
options((BaseMessage) -> List<CometChatMessageOption>?)?Replace all defaults (takes precedence over addOptions; return null to keep defaults)
onMessageOptionClick((BaseMessage, optionId: String, optionName: String) -> Unit)?Click handler

Use addOptions for a Forward-style action so the built-in Reply/Copy/Edit/Delete survive:

import com.cometchat.uikit.core.domain.model.CometChatMessageOption

CometChatMessageList(
    user = user,
    addOptions = { message ->            // ✅ appended to defaults
        listOf(
            CometChatMessageOption(
                id = "forward",
                title = "Forward",
                icon = R.drawable.ic_forward,
                onClick = { /* open forward picker for `message` */ }
            )
        )
    }
    // ❌ options = { listOf(forward) }   // would DROP reply/copy/edit/delete
)

CometChatMessageOption(id, title, titleColor, icon, iconTintColor, …, onClick) is a data class at CometChatMessageOption.kt:21. Resolution order is verified in CometChatMessageListViewModel.resolveMessageOptions (:817-827): setOptions result wins if non-null, else defaultOptions + addOptions.

1.11 MessageTemplate via the DataSource (docs-taught alternative to BubbleFactory)

⚠️ Verify before using — symbols not found in the GA v6 Compose source. CometChatUIKit.getDataSource(), getMessageTemplates(), CometChatMessageTemplate, and a templates parameter on the Compose CometChatMessageList appear in the Android docs but are absent from the verified com.cometchat.uikit.compose GA source (they're the legacy com.cometchat.chatuikit v5-shaped API; the Compose list only exposes bubbleFactories). Treat this section as a docs reference that may not compile against your installed v6 kit — prefer BubbleFactory (the confirmed v6-native path) for all new code.

The BubbleFactory / slot-lambda surface above is the primary v6 customization path. The Android docs also teach a MessageTemplate path through the data source (message-template) — fetch the existing templates, mutate the one you want, then re-apply (append, never replace with a single-item list):

// 1. fetch existing templates from the data source
val templates = CometChatUIKit.getDataSource()
    .getMessageTemplates(additionParameter)   // pass the list's additionParameter
    .toMutableList()

// 2. mutate the template you want (or create a new one for a custom type)
templates.find { it.type == UIKitConstants.MessageType.TEXT }
    ?.setContentView { baseMessage, alignment ->
        Text(text = (baseMessage as? TextMessage)?.text ?: "")
    }
// new custom type:
val contact = CometChatMessageTemplate().apply {
    setType("contact"); setCategory(UIKitConstants.MessageCategory.CUSTOM)
    setContentView { baseMessage, _ -> /* custom composable */ }
}
templates.add(contact)

// 3. re-apply — Compose passes the list to CometChatMessageList
CometChatMessageList(user = user, templates = templates)

Compose templates expose lambda slot setters (setContentView, setHeaderView, setFooterView, setBottomView, setStatusInfoView, setBubbleView, setOptions) mirroring §2's slots. Prefer BubbleFactory for new code; use getMessageTemplates(...) when you want to start from the kit's defaults and tweak one type. (This overlaps the custom-message topic.)

2. Slot Lambda Parameters (Per-Slot Overrides)

Override individual slots across ALL message types directly on CometChatMessageList:

CometChatMessageList(
    user = user,

    // Custom avatar for all messages
    leadingView = { message, alignment ->
        if (alignment == MessageAlignment.LEFT) {
            AsyncImage(
                model = message.sender?.avatar,
                contentDescription = null,
                modifier = Modifier.size(32.dp).clip(CircleShape)
            )
        }
    },

    // Custom timestamp for all messages
    statusInfoView = { message, alignment ->
        Text(
            text = formatTime(message.sentAt),
            style = CometChatTheme.typography.caption1Regular,
            color = CometChatTheme.colorScheme.textColorTertiary
        )
    },

    // Custom footer for all messages
    footerView = { message, alignment ->
        // Custom reactions display
        ReactionsRow(message = message)
    }
)

2.1 All Slot Parameters

ParameterTypeSlot
leadingView@Composable (BaseMessage, MessageAlignment) -> UnitAvatar
headerView@Composable (BaseMessage, MessageAlignment) -> UnitSender name
replyView@Composable (BaseMessage, MessageAlignment) -> UnitReply preview
contentView@Composable (BaseMessage, MessageAlignment) -> UnitMain content
bottomView@Composable (BaseMessage, MessageAlignment) -> UnitModeration
statusInfoView@Composable (BaseMessage, MessageAlignment) -> UnitTimestamp/receipts
threadView@Composable (BaseMessage, MessageAlignment) -> UnitThread indicator
footerView@Composable (BaseMessage, MessageAlignment) -> UnitReactions

2.2 Priority Order

For each slot, the resolution order is:

  1. Explicit slot lambda parameter (highest priority)
  2. BubbleFactory slot method
  3. Internal default rendering

3. Style Classes

3.1 Using Style Classes

CometChatConversations(
    style = CometChatConversationsStyle.default(
        backgroundColor = Color(0xFFF5F5F5),
        titleTextColor = Color.Black,
        titleTextStyle = CometChatTheme.typography.heading1Bold,
        separatorColor = Color.LightGray,
        itemStyle = CometChatConversationListItemStyle.default(
            titleTextColor = Color.DarkGray
        )
    )
)

3.2 Style Class Pattern

All style classes follow this pattern:

@Immutable
data class CometChatConversationsStyle(
    val backgroundColor: Color,
    val titleTextColor: Color,
    val titleTextStyle: TextStyle,
    // ... many properties
) {
    companion object {
        @Composable
        fun default(
            backgroundColor: Color = CometChatTheme.colorScheme.backgroundColor1,
            titleTextColor: Color = CometChatTheme.colorScheme.textColorPrimary,
            // ... defaults from CometChatTheme
        ): CometChatConversationsStyle = CometChatConversationsStyle(/* ... */)
    }
}

Use Companion.default() with named parameter overrides — never call the data class constructor directly.

3.3 Nested Styles

Some styles contain nested style classes:

CometChatConversationsStyle.default(
    itemStyle = CometChatConversationListItemStyle.default(/* ... */),
    popupMenuStyle = CometChatPopupMenuStyle.default(/* ... */),
    emptyStateStyle = CometChatEmptyStateStyle.default(/* ... */),
    errorStateStyle = CometChatErrorStateStyle.default(/* ... */),
    loadingStateStyle = CometChatLoadingStateStyle.default(/* ... */),
    dialogStyle = CometChatDialogStyle.default(/* ... */)
)

4. Custom State Views

CometChatMessageList(
    user = user,
    loadingView = { CircularProgressIndicator() },
    emptyView = { Text("No messages yet") },
    errorView = { Text("Something went wrong") }
)

5. v5 → v6 Migration

v5 Patternv6 Pattern
ChatConfigurator.enable(decorator)CometChatMessageList(bubbleFactories = listOf(...))
DataSource template methodsBubbleFactory slot methods
Global decorator chainPer-component factory registration
No bubble replacementgetBubbleView() for complete replacement
Theme-level styling onlyPer-factory getBubbleStyle()

Text formatters (inline mentions / URLs / custom tokens)

Style/transform message text inline by passing textFormatters to the composer/list. The kit ships CometChatMentionsFormatter + CometChatRichTextFormatter (subclasses of the abstract CometChatTextFormatter) in com.cometchat.uikit.compose.presentation.shared.formatters. Default behavior (a mentions formatter) applies when you pass nothing — so to ADD a custom one, include the defaults too.

import com.cometchat.uikit.compose.presentation.shared.formatters.CometChatMentionsFormatter
import com.cometchat.uikit.compose.presentation.shared.formatters.CometChatTextFormatter

CometChatMessageComposer(
    user = user,
    textFormatters = listOf(CometChatMentionsFormatter(context) /* , YourFormatter(context) */),
)
// CometChatMessageList also takes textFormatters: List<CometChatTextFormatter>.

For a custom @/#/! token: subclass CometChatTextFormatter(trackingCharacter) and implement its search() + view hooks (see the kit's formatters/ source + the per-platform docs).

Hard rules

  • BubbleFactory is an interface — implement it, don't extend an abstract class
  • getCategory() and getType() are REQUIRED — they identify which message type the factory handles
  • When getBubbleView() returns non-null, ALL other slot methods are ignored for that message type
  • Slot lambda parameters on CometChatMessageList override BubbleFactory slot methods — use lambdas for cross-type overrides, factories for per-type overrides
  • Style classes must use Companion.default() factory — the data class constructor requires ALL parameters
  • Do NOT confuse BubbleFactory (UI rendering) with DataSource (data fetching) — they are separate layers

Sound (in-app message + call sounds)

Sound is a customization sub-dimension. The UI Kit plays incoming/outgoing message + call sounds via CometChatSoundManager — mute it, swap custom audio, or play a specific sound. The full API + recipe lives in cometchat-android-v6-compose-theming (Sound section). Verify the access path against the installed kit before relying on it.

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.