{"id":"b9624eb7-f4d8-4b75-8de3-566c6ecbcf31","shortId":"3hPX4y","kind":"skill","title":"cometchat-android-v6-kotlin-customization","tagline":"CometChat Android UIKit v6 Kotlin Views customization — BubbleFactory abstract class, BubbleViewProvider, style classes, and per-slot overrides","description":"> **Companion skills:** cometchat-android-v6-compose-customization (Compose equivalent), cometchat-android-v6-kotlin-components, cometchat-android-v6-kotlin-theming, cometchat-android-v6-extensions (DataSource layer), cometchat-android-v6-events\n\n## Purpose\n\nCustomize CometChat Kotlin Views components — override message bubble rendering with BubbleFactory, use BubbleViewProvider for per-slot overrides, and apply style classes. This is the v6 replacement for v5's DataSource/ChatConfigurator pattern.\n\n## Use this skill when\n\n- Creating custom message bubble rendering for specific message types\n- Overriding individual bubble slots (avatar, header, footer, content, etc.)\n- Applying custom styles to components\n- Replacing the entire bubble layout for a message type\n\n## Do not use this skill when\n\n- Working with Compose customization (use `cometchat-android-v6-compose-customization`)\n- Changing theme colors globally (use `cometchat-android-v6-kotlin-theming`)\n- Extending the data layer (use `cometchat-android-v6-extensions`)\n\n## 1. BubbleFactory (Message Bubble Customization)\n\n`BubbleFactory` is an abstract class in `com.cometchat.uikit.kotlin.presentation.shared.messagebubble`. Each subclass handles a specific message type.\n\n### 1.1 Creating a Custom BubbleFactory\n\n```kotlin\nimport com.cometchat.uikit.kotlin.presentation.shared.messagebubble.BubbleFactory\nimport com.cometchat.chat.constants.CometChatConstants\nimport com.cometchat.chat.models.BaseMessage\nimport com.cometchat.uikit.core.constants.UIKitConstants.MessageBubbleAlignment\n\nclass LocationBubbleFactory : BubbleFactory() {\n\n    override fun getCategory(): String = CometChatConstants.CATEGORY_CUSTOM\n    override fun getType(): String = \"location\"\n\n    override fun createContentView(context: Context): View {\n        // Called ONCE when ViewHolder is created — message NOT available here\n        return LocationMapView(context)\n    }\n\n    override fun bindContentView(\n        view: View,\n        message: BaseMessage,\n        alignment: MessageBubbleAlignment,\n        holder: RecyclerView.ViewHolder?,\n        position: Int\n    ) {\n        // Called EVERY TIME a message is displayed\n        val mapView = view as LocationMapView\n        val metadata = message.metadata\n        mapView.setLocation(\n            metadata?.optDouble(\"latitude\") ?: 0.0,\n            metadata?.optDouble(\"longitude\") ?: 0.0\n        )\n    }\n}\n```\n\n### 1.2 Registering BubbleFactories\n\n```kotlin\nval messageList = findViewById<CometChatMessageList>(R.id.messageList)\nmessageList.setBubbleFactories(listOf(\n    LocationBubbleFactory(),\n    PaymentBubbleFactory()\n))\n```\n\nThe list is converted to a map keyed by `\"category_type\"` internally.\n\n### 1.3 Complete Bubble Replacement\n\nOverride `createBubbleView()` / `bindBubbleView()` to replace the ENTIRE bubble:\n\n```kotlin\nclass CustomBubbleFactory : BubbleFactory() {\n    override fun getCategory(): String = \"custom\"\n    override fun getType(): String = \"payment\"\n\n    override fun createBubbleView(context: Context): View? {\n        // Return non-null to replace the entire CometChatMessageBubble\n        return PaymentCardView(context)\n    }\n\n    override fun bindBubbleView(\n        view: View,\n        message: BaseMessage,\n        alignment: MessageBubbleAlignment,\n        holder: RecyclerView.ViewHolder?,\n        position: Int\n    ) {\n        (view as PaymentCardView).bind(message)\n    }\n}\n```\n\nWhen `createBubbleView()` returns non-null, `createContentView()` and all other slot methods are ignored.\n\n### 1.4 Slot Methods\n\nAll follow the create/bind pattern for RecyclerView efficiency:\n\n| Create Method | Bind Method | Slot |\n|---|---|---|\n| `createContentView(ctx): View` | `bindContentView(view, msg, align, holder, pos)` | Main content |\n| `createLeadingView(ctx): View?` | `bindLeadingView(view, msg, align)` | Avatar |\n| `createHeaderView(ctx): View?` | `bindHeaderView(view, msg, align)` | Sender name |\n| `createReplyView(ctx): View?` | `bindReplyView(view, msg, align)` | Reply preview |\n| `createBottomView(ctx): View?` | `bindBottomView(view, msg, align)` | Reactions |\n| `createStatusInfoView(ctx): View?` | `bindStatusInfoView(view, msg, align)` | Timestamp/receipts |\n| `createThreadView(ctx): View?` | `bindThreadView(view, msg, align)` | Thread indicator |\n| `createFooterView(ctx): View?` | `bindFooterView(view, msg, align)` | Footer |\n\n**Critical:** `create*()` methods are called when the ViewHolder is created — the message is NOT available. All message-specific logic goes in `bind*()`.\n\n### 1.5 Style Override\n\n```kotlin\noverride fun getBubbleStyle(\n    message: BaseMessage,\n    alignment: MessageBubbleAlignment\n): CometChatMessageBubbleStyle? {\n    // Highest priority in the 3-tier style chain\n    return CometChatMessageBubbleStyle(/* custom style */)\n}\n```\n\n### 1.6 Lifecycle\n\n```kotlin\noverride fun onViewRecycled(contentView: View) {\n    // Clean up resources (image loads, animations, media playback)\n}\n```\n\n### 1.7 Factory Key\n\n```kotlin\n// Get the factory key for a message\nval key = BubbleFactory.getFactoryKey(message) // \"category_type\" or \"deleted\"\n\n// Create a key manually\nval key = BubbleFactory.getKey(\"custom\", \"location\") // \"custom_location\"\n```\n\n## 2. BubbleViewProvider (Per-Slot Overrides)\n\n`BubbleViewProvider` is a simpler interface for overriding individual slots across ALL message types.\n\n### 2.1 Interface\n\n```kotlin\ninterface BubbleViewProvider {\n    fun createView(\n        context: Context,\n        message: BaseMessage,\n        alignment: MessageBubbleAlignment\n    ): View?\n\n    fun bindView(\n        view: View,\n        message: BaseMessage,\n        alignment: MessageBubbleAlignment\n    )\n}\n```\n\n### 2.2 Usage\n\n```kotlin\nval messageList = findViewById<CometChatMessageList>(R.id.messageList)\n\n// Custom avatar for all messages\nmessageList.setLeadingViewProvider(object : BubbleViewProvider {\n    override fun createView(\n        context: Context,\n        message: BaseMessage,\n        alignment: MessageBubbleAlignment\n    ): View? {\n        return if (alignment == MessageBubbleAlignment.LEFT) {\n            ImageView(context).apply {\n                layoutParams = ViewGroup.LayoutParams(32.dp, 32.dp)\n            }\n        } else null\n    }\n\n    override fun bindView(\n        view: View,\n        message: BaseMessage,\n        alignment: MessageBubbleAlignment\n    ) {\n        (view as ImageView).load(message.sender?.avatar)\n    }\n})\n```\n\n### 2.3 All Provider Setters\n\n| Method | Slot |\n|---|---|\n| `setLeadingViewProvider(provider)` | Avatar |\n| `setHeaderViewProvider(provider)` | Sender name |\n| `setContentViewProvider(provider)` | Main content |\n| `setReplyViewProvider(provider)` | Reply preview |\n| `setBottomViewProvider(provider)` | Reactions |\n| `setStatusInfoViewProvider(provider)` | Timestamp/receipts |\n| `setThreadViewProvider(provider)` | Thread indicator |\n| `setFooterViewProvider(provider)` | Footer |\n\n### 2.4 Priority Order\n\nFor each slot:\n1. Explicit `BubbleViewProvider` (highest priority)\n2. `BubbleFactory` slot method\n3. Internal default rendering\n\n## 3. Component-Level Customization\n\n### 3.1 Header/Footer Views on MessageList\n\n```kotlin\n// Custom header above the message list\nmessageList.setHeaderView(myCustomHeaderView)\n\n// Custom footer below the message list\nmessageList.setFooterView(myCustomFooterView)\n```\n\n### 3.2 Style Classes\n\n```kotlin\n// Style classes resolve from XML attrs and CometChatTheme singleton\nval style = CometChatMessageListStyle(/* properties */)\nmessageList.setStyle(style)\n```\n\n## 4. v5 → v6 Migration\n\n| v5 Pattern | v6 Pattern |\n|---|---|\n| `ChatConfigurator.enable(decorator)` | `messageList.setBubbleFactories(listOf(...))` |\n| DataSource template methods | BubbleFactory slot methods |\n| Global decorator chain | Per-component factory registration |\n| No bubble replacement | `createBubbleView()` for complete replacement |\n| Theme-level styling only | Per-factory `getBubbleStyle()` |\n\n## Hard rules\n\n- BubbleFactory is an `abstract class` — extend it with `BubbleFactory()`\n- `create*()` methods are called WITHOUT a message — do NOT access message data in create methods\n- `bind*()` methods receive the message — all message-specific logic goes here\n- BubbleViewProvider overrides take precedence over BubbleFactory slot methods\n- For per-type content customization, use BubbleFactory. For cross-type slot overrides, use BubbleViewProvider\n- `onViewRecycled()` is critical for cleaning up image loads, animations, or media playback\n- Do NOT confuse BubbleFactory (UI rendering) with DataSource (data fetching) — they are separate layers","tags":["cometchat","android","kotlin","customization","skills","agent-skills","ai-agent","chat","claude-code","cursor","messaging","nextjs"],"capabilities":["skill","source-cometchat","skill-cometchat-android-v6-kotlin-customization","topic-agent-skills","topic-ai-agent","topic-chat","topic-claude-code","topic-cometchat","topic-cursor","topic-messaging","topic-nextjs","topic-react","topic-react-native","topic-ui-kit"],"categories":["cometchat-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/cometchat/cometchat-skills/cometchat-android-v6-kotlin-customization","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add cometchat/cometchat-skills","source_repo":"https://github.com/cometchat/cometchat-skills","install_from":"skills.sh"}},"qualityScore":"0.463","qualityRationale":"deterministic score 0.46 from registry signals: · indexed on github topic:agent-skills · 27 github stars · SKILL.md body (8,584 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-18T19:04:46.864Z","embedding":null,"createdAt":"2026-05-07T13:05:06.136Z","updatedAt":"2026-05-18T19:04:46.864Z","lastSeenAt":"2026-05-18T19:04:46.864Z","tsv":"'0.0':264,268 '1':166,682 '1.1':185 '1.2':269 '1.3':293 '1.4':369 '1.5':478 '1.6':502 '1.7':518 '2':548,687 '2.1':567 '2.2':589 '2.3':642 '2.4':676 '3':494,691,695 '3.1':700 '3.2':722 '32.dp':623,624 '4':741 'abstract':15,174,788 'access':803 'across':563 'align':239,344,391,402,410,419,428,436,444,453,487,578,587,611,616,634 'android':3,8,29,37,43,49,56,141,152,163 'anim':515,853 'appli':79,114,620 'attr':731 'avail':227,469 'avatar':109,403,597,641,650 'basemessag':238,343,486,577,586,610,633 'bind':353,382,477,809 'bindbottomview':425 'bindbubbleview':299,339 'bindcontentview':234,388 'bindfooterview':450 'bindheaderview':407 'bindleadingview':399 'bindreplyview':416 'bindstatusinfoview':433 'bindthreadview':441 'bindview':582,629 'bubbl':67,99,107,122,169,295,304,768 'bubblefactori':14,70,167,171,189,201,271,308,688,756,785,793,826,836,860 'bubblefactory.getfactorykey':531 'bubblefactory.getkey':543 'bubbleviewprovid':17,72,549,554,571,603,684,821,844 'call':219,245,459,797 'categori':290,533 'chain':497,761 'chang':145 'chatconfigurator.enable':749 'class':16,19,81,175,199,306,724,727,789 'clean':510,849 'color':147 'com.cometchat.chat.constants.cometchatconstants':194 'com.cometchat.chat.models.basemessage':196 'com.cometchat.uikit.core.constants.uikitconstants.messagebubblealignment':198 'com.cometchat.uikit.kotlin.presentation.shared.messagebubble':177 'com.cometchat.uikit.kotlin.presentation.shared.messagebubble.bubblefactory':192 'cometchat':2,7,28,36,42,48,55,61,140,151,162 'cometchat-android-v6-compose-customization':27,139 'cometchat-android-v6-events':54 'cometchat-android-v6-extensions':47,161 'cometchat-android-v6-kotlin-components':35 'cometchat-android-v6-kotlin-customization':1 'cometchat-android-v6-kotlin-theming':41,150 'cometchatconstants.category':206 'cometchatmessagebubbl':333 'cometchatmessagebubblestyl':489,499 'cometchatmessageliststyl':737 'cometchatthem':733 'companion':25 'complet':294,772 'compon':40,64,118,697,764 'component-level':696 'compos':31,33,136,143 'confus':859 'content':112,395,658,833 'contentview':508 'context':216,217,231,322,323,336,574,575,607,608,619 'convert':284 'creat':96,186,224,380,456,464,537,794,807 'create/bind':375 'createbottomview':422 'createbubbleview':298,321,356,770 'createcontentview':215,361,385 'createfooterview':447 'createheaderview':404 'createleadingview':396 'createreplyview':413 'createstatusinfoview':430 'createthreadview':438 'createview':573,606 'critic':455,847 'cross':839 'cross-typ':838 'ctx':386,397,405,414,423,431,439,448 'custom':6,13,32,60,97,115,137,144,170,188,207,313,500,544,546,596,699,706,714,834 'custombubblefactori':307 'data':158,805,865 'datasourc':52,753,864 'datasource/chatconfigurator':90 'decor':750,760 'default':693 'delet':536 'display':251 'effici':379 'els':625 'entir':121,303,332 'equival':34 'etc':113 'event':58 'everi':246 'explicit':683 'extend':156,790 'extens':51,165 'factori':519,524,765,781 'fetch':866 'findviewbyid':275,594 'follow':373 'footer':111,454,675,715 'fun':203,209,214,233,310,315,320,338,483,506,572,581,605,628 'get':522 'getbubblestyl':484,782 'getcategori':204,311 'gettyp':210,316 'global':148,759 'goe':475,819 'handl':180 'hard':783 'header':110,707 'header/footer':701 'highest':490,685 'holder':241,346,392 'ignor':368 'imag':513,851 'imageview':618,638 'import':191,193,195,197 'indic':446,672 'individu':106,561 'int':244,349 'interfac':558,568,570 'intern':292,692 'key':288,520,525,530,539,542 'kotlin':5,11,39,45,62,154,190,272,305,481,504,521,569,591,705,725 'latitud':263 'layer':53,159,870 'layout':123 'layoutparam':621 'level':698,776 'lifecycl':503 'list':282,711,719 'listof':278,752 'load':514,639,852 'locat':212,545,547 'locationbubblefactori':200,279 'locationmapview':230,256 'logic':474,818 'longitud':267 'main':394,657 'manual':540 'map':287 'mapview':253 'mapview.setlocation':260 'media':516,855 'messag':66,98,103,126,168,183,225,237,249,342,354,466,472,485,528,532,565,576,585,600,609,632,710,718,800,804,813,816 'message-specif':471,815 'message.metadata':259 'message.sender':640 'messagebubblealign':240,345,488,579,588,612,635 'messagebubblealignment.left':617 'messagelist':274,593,704 'messagelist.setbubblefactories':277,751 'messagelist.setfooterview':720 'messagelist.setheaderview':712 'messagelist.setleadingviewprovider':601 'messagelist.setstyle':739 'metadata':258,261,265 'method':366,371,381,383,457,646,690,755,758,795,808,810,828 'migrat':744 'msg':390,401,409,418,427,435,443,452 'mycustomfooterview':721 'mycustomheaderview':713 'name':412,654 'non':327,359 'non-nul':326,358 'null':328,360,626 'object':602 'onviewrecycl':507,845 'optdoubl':262,266 'order':678 'overrid':24,65,77,105,202,208,213,232,297,309,314,319,337,480,482,505,553,560,604,627,822,842 'pattern':91,376,746,748 'payment':318 'paymentbubblefactori':280 'paymentcardview':335,352 'per':22,75,551,763,780,831 'per-compon':762 'per-factori':779 'per-slot':21,74,550 'per-typ':830 'playback':517,856 'pos':393 'posit':243,348 'preced':824 'preview':421,662 'prioriti':491,677,686 'properti':738 'provid':644,649,652,656,660,664,667,670,674 'purpos':59 'r.id.messagelist':276,595 'reaction':429,665 'receiv':811 'recyclerview':378 'recyclerview.viewholder':242,347 'regist':270 'registr':766 'render':68,100,694,862 'replac':86,119,296,301,330,769,773 'repli':420,661 'resolv':728 'resourc':512 'return':229,325,334,357,498,614 'rule':784 'sender':411,653 'separ':869 'setbottomviewprovid':663 'setcontentviewprovid':655 'setfooterviewprovid':673 'setheaderviewprovid':651 'setleadingviewprovid':648 'setreplyviewprovid':659 'setstatusinfoviewprovid':666 'setter':645 'setthreadviewprovid':669 'simpler':557 'singleton':734 'skill':26,94,132 'skill-cometchat-android-v6-kotlin-customization' 'slot':23,76,108,365,370,384,552,562,647,681,689,757,827,841 'source-cometchat' 'specif':102,182,473,817 'string':205,211,312,317 'style':18,80,116,479,496,501,723,726,736,740,777 'subclass':179 'take':823 'templat':754 'theme':46,146,155,775 'theme-level':774 'thread':445,671 'tier':495 'time':247 'timestamp/receipts':437,668 'topic-agent-skills' 'topic-ai-agent' 'topic-chat' 'topic-claude-code' 'topic-cometchat' 'topic-cursor' 'topic-messaging' 'topic-nextjs' 'topic-react' 'topic-react-native' 'topic-ui-kit' 'type':104,127,184,291,534,566,832,840 'ui':861 'uikit':9 'usag':590 'use':71,92,130,138,149,160,835,843 'v5':88,742,745 'v6':4,10,30,38,44,50,57,85,142,153,164,743,747 'val':252,257,273,529,541,592,735 'view':12,63,218,235,236,254,324,340,341,350,387,389,398,400,406,408,415,417,424,426,432,434,440,442,449,451,509,580,583,584,613,630,631,636,702 'viewgroup.layoutparams':622 'viewhold':222,462 'without':798 'work':134 'xml':730","prices":[{"id":"b7ee2dec-115f-4301-86e7-d218a3900a94","listingId":"b9624eb7-f4d8-4b75-8de3-566c6ecbcf31","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"cometchat","category":"cometchat-skills","install_from":"skills.sh"},"createdAt":"2026-05-07T13:05:06.136Z"}],"sources":[{"listingId":"b9624eb7-f4d8-4b75-8de3-566c6ecbcf31","source":"github","sourceId":"cometchat/cometchat-skills/cometchat-android-v6-kotlin-customization","sourceUrl":"https://github.com/cometchat/cometchat-skills/tree/main/skills/cometchat-android-v6-kotlin-customization","isPrimary":false,"firstSeenAt":"2026-05-07T13:05:06.136Z","lastSeenAt":"2026-05-18T19:04:46.864Z"}],"details":{"listingId":"b9624eb7-f4d8-4b75-8de3-566c6ecbcf31","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"cometchat","slug":"cometchat-android-v6-kotlin-customization","github":{"repo":"cometchat/cometchat-skills","stars":27,"topics":["agent-skills","ai-agent","chat","claude-code","cometchat","cursor","messaging","nextjs","react","react-native","ui-kit"],"license":null,"html_url":"https://github.com/cometchat/cometchat-skills","pushed_at":"2026-05-18T05:04:24Z","description":"Add CometChat chat to any React, Next.js, React Native, Angular, Android, iOS, or Flutter project through your AI coding agent. Works with Claude Code, Cursor, Codex, VS Code Copilot, Windsurf, Cline, Kiro, and 50+ more agents.","skill_md_sha":"7ba4d2354f25008a747075e2cf386fc207f41943","skill_md_path":"skills/cometchat-android-v6-kotlin-customization/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/cometchat/cometchat-skills/tree/main/skills/cometchat-android-v6-kotlin-customization"},"layout":"multi","source":"github","category":"cometchat-skills","frontmatter":{"name":"cometchat-android-v6-kotlin-customization","license":"MIT","description":"CometChat Android UIKit v6 Kotlin Views customization — BubbleFactory abstract class, BubbleViewProvider, style classes, and per-slot overrides","compatibility":"Android 9.0+ (API 28); Kotlin 1.9+; com.cometchat:chatuikit-kotlin-android:6.x"},"skills_sh_url":"https://skills.sh/cometchat/cometchat-skills/cometchat-android-v6-kotlin-customization"},"updatedAt":"2026-05-18T19:04:46.864Z"}}