{"id":"6e430f20-6c22-4f56-8e60-729d3fe4180b","shortId":"35Swnp","kind":"skill","title":"cometchat-flutter-v6-conversations","tagline":"Use when implementing the conversations list with CometChat Flutter UIKit v6. Triggers on mentions of CometChatConversations, ConversationsBloc, ConversationsState, ConversationsLoaded, ConversationsServiceLocator, conversation list, recent chats, unread count, typing indicator i","description":"# CometChat Flutter UIKit — Conversations\n\nThe `CometChatConversations` widget displays a list of recent conversations with real-time updates.\n\n## Basic Usage\n\n```dart\nCometChatConversations(\n  onItemTap: (conversation) {\n    final user = conversation.conversationWith is User\n        ? conversation.conversationWith as User : null;\n    final group = conversation.conversationWith is Group\n        ? conversation.conversationWith as Group : null;\n    Navigator.push(context, MaterialPageRoute(\n      builder: (_) => MessagesScreen(user: user, group: group),\n    ));\n  },\n)\n```\n\n## Architecture\n\n```\nconversations/\n├── bloc/\n│   ├── conversations_bloc.dart    # SDK listeners, real-time updates, O(1) lookups\n│   ├── conversations_event.dart   # LoadConversations, DeleteConversation, etc.\n│   └── conversations_state.dart   # ConversationsInitial/Loading/Loaded/Empty/Error\n├── domain/usecases/               # GetConversationsUseCase, DeleteConversationUseCase, etc.\n├── data/                          # Repository + DataSources (remote + local)\n├── di/                            # ConversationsServiceLocator (singleton)\n└── widgets/                       # List item, subtitle, trailing, empty/error/loading views\n```\n\n## State Classes\n\n```dart\nConversationsInitial    // Before any data loaded\nConversationsLoading    // Fetching initial list\nConversationsLoaded     // Has data: conversations, hasMore, selectedConversations, isLoadingMore\nConversationsEmpty      // No conversations exist\nConversationsError      // Error with message + optional previousConversations\n```\n\n`ConversationsLoaded` uses a monotonically increasing `_version` counter to guarantee unique emissions even when conversation IDs haven't changed (SDK's `Conversation.==` only compares `conversationId`).\n\n## Key Events\n\n| Event | Purpose |\n|-------|---------|\n| `LoadConversations({silent})` | Initial load. `silent: true` keeps existing list visible during refresh. |\n| `LoadMoreConversations` | Pagination (scroll to bottom) |\n| `DeleteConversation(id)` | Calls SDK to delete |\n| `RemoveConversation(id)` | Remove from list without SDK call (e.g., after group kick) |\n| `SetActiveConversation(id)` | Track which conversation is open |\n| `UpdateConversation(id, conversation)` | Update a specific conversation's data |\n| `ResetUnreadCount(id)` | Clear unread badge when user reads messages |\n\n## Real-Time Features\n\nThe BLoC automatically registers SDK listeners for:\n- New messages → updates last message + moves conversation to top\n- Typing indicators → per-conversation `ValueNotifier<List<TypingIndicator>>`\n- User presence → online/offline status\n- Read/delivery receipts → receipt icons\n- Group events → member join/leave/kick/ban\n- Connection state → reconnect triggers silent refresh\n\n### Typing Indicators (ValueNotifier Pattern)\n\n```dart\n// In your custom list item, use ValueListenableBuilder for isolated rebuilds:\nValueListenableBuilder<List<TypingIndicator>>(\n  valueListenable: conversationsBloc.getTypingNotifier(conversation.conversationId!),\n  builder: (context, typingList, child) {\n    if (typingList.isEmpty) return _buildLastMessage();\n    return Text('${typingList.first.sender?.name} is typing...');\n  },\n)\n```\n\n## Customization — View Slots\n\n```dart\nCometChatConversations(\n  // Replace entire list item\n  listItemView: (conversation) => MyCustomListItem(conversation),\n\n  // Replace just the subtitle — two-arg builder (BuildContext, Conversation)\n  subtitleView: (context, conversation) => Text(conversation.lastMessage?.text ?? ''),\n\n  // Replace trailing (time + badge) — SINGLE-arg builder. Note the asymmetry:\n  // subtitleView/leadingView/titleView take (BuildContext, Conversation),\n  // but trailingView takes just (Conversation).\n  trailingView: (conversation) => MyTrailingWidget(conversation),\n\n  // Style overrides\n  conversationsStyle: CometChatConversationsStyle(\n    backgroundColor: colors.background1,\n    titleStyle: typography.heading3?.bold,\n  ),\n\n  // Configuration\n  usersStatusVisibility: true,\n  receiptsVisibility: true,\n  deleteConversationOptionVisibility: true,\n  hideAppbar: false,\n  showBackButton: false,\n\n  // Text formatters for subtitle preview\n  textFormatters: [\n    CometChatMentionsFormatter(),\n    MarkdownTextFormatter(),\n  ],\n)\n```\n\n## Custom ConversationsRequest\n\n```dart\nCometChatConversations(\n  conversationsRequestBuilder: ConversationsRequestBuilder()\n    ..limit = 30\n    ..conversationType = 'user'  // Only 1:1 chats\n    ..withTags = true\n    ..tags = ['important'],\n)\n```\n\n## Gotchas\n\n- `ConversationsLoaded` uses a `_version` counter because SDK's `Conversation.==` only compares `conversationId`. Without it, BLoC considers two lists with same IDs as equal even when `lastMessage` or `unreadMessageCount` changed, and skips the emission.\n- The BLoC uses `_conversationIndexMap` for O(1) lookups. If you maintain your own list outside the BLoC, keep a parallel Map for performance.\n- `silent: true` on `LoadConversations` keeps the existing list visible during refresh — use this for background→foreground and reconnect scenarios, not initial load.\n- Typing indicators use `ValueNotifier` per conversation, NOT BLoC state. This prevents the entire list from rebuilding when one person types.\n\n## Anti-Patterns\n\n```dart\n// ❌ WRONG — navigating with raw conversation object\nonItemTap: (conv) => Navigator.push(context, MaterialPageRoute(\n  builder: (_) => MessagesScreen(conversation: conv), // MessagesScreen expects user OR group\n))\n\n// ✅ CORRECT — extract user/group from conversation\nonItemTap: (conv) {\n  final user = conv.conversationWith is User ? conv.conversationWith as User : null;\n  final group = conv.conversationWith is Group ? conv.conversationWith as Group : null;\n  Navigator.push(context, MaterialPageRoute(\n    builder: (_) => MessagesScreen(user: user, group: group),\n  ));\n}\n```\n\n```dart\n// ❌ WRONG — creating BLoC without initializing ServiceLocator\nfinal bloc = ConversationsBloc(/* ... */);\n\n// ✅ CORRECT — setup first (widget does this automatically)\nConversationsServiceLocator.instance.setup();\nfinal bloc = ConversationsBloc(\n  getLoggedInUserUseCase: ConversationsServiceLocator.instance.getLoggedInUserUseCase,\n  getConversationUseCase: ConversationsServiceLocator.instance.getConversationUseCase,\n  markAsDeliveredUseCase: ConversationsServiceLocator.instance.markAsDeliveredUseCase,\n  deleteConversationUseCase: ConversationsServiceLocator.instance.deleteConversationUseCase,\n);\n```\n\n```dart\n// ❌ WRONG — rebuilding entire list for typing indicator\nBlocBuilder<ConversationsBloc, ConversationsState>(\n  builder: (context, state) {\n    // This rebuilds ALL items when ANY state changes\n  },\n)\n\n// ✅ CORRECT — use ValueListenableBuilder per item\nValueListenableBuilder<List<TypingIndicator>>(\n  valueListenable: bloc.getTypingNotifier(conversationId),\n  builder: (_, typingList, __) { /* Only this item rebuilds */ },\n)\n```\n\n## Checklist\n\n- [ ] `onItemTap` extracts `User`/`Group` from `conversation.conversationWith`\n- [ ] `subscriptionType` set in UIKitSettings (required for real-time updates)\n- [ ] Typing indicators use `ValueListenableBuilder`, not BLoC state\n- [ ] Custom list items use `CometChatThemeHelper` for colors\n- [ ] Text formatters passed if using mentions or markdown in subtitle preview\n- [ ] `deleteConversationOptionVisibility` set based on app requirements","tags":["cometchat","flutter","conversations","skills","agent-skills","ai-agent","chat","claude-code","cursor","messaging","nextjs","react"],"capabilities":["skill","source-cometchat","skill-cometchat-flutter-v6-conversations","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-flutter-v6-conversations","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 (7,366 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:51.181Z","embedding":null,"createdAt":"2026-05-07T13:05:10.841Z","updatedAt":"2026-05-18T19:04:51.181Z","lastSeenAt":"2026-05-18T19:04:51.181Z","tsv":"'1':97,412,413,459 '30':408 'anti':519 'anti-pattern':518 'app':689 'architectur':86 'arg':339,355 'asymmetri':359 'automat':247,592 'background':490 'backgroundcolor':377 'badg':236,352 'base':687 'basic':53 'bloc':88,246,434,454,469,505,579,584,595,665 'bloc.gettypingnotifier':635 'blocbuild':613 'bold':381 'bottom':197 'buildcontext':341,362 'builder':80,306,340,356,533,570,616,637 'buildlastmessag':313 'call':200,211 'chang':170,448,626 'chat':29,414 'checklist':643 'child':309 'class':125 'clear':234 'color':673 'colors.background1':378 'cometchat':2,13,35 'cometchat-flutter-v6-conversations':1 'cometchatconvers':21,40,56,324,404 'cometchatconversationsstyl':376 'cometchatmentionsformatt':399 'cometchatthemehelp':671 'compar':175,430 'configur':382 'connect':280 'consid':435 'context':78,307,344,531,568,617 'conv':529,536,548 'conv.conversationwith':551,554,560,563 'convers':5,10,26,38,47,58,87,139,145,166,173,220,225,229,258,265,330,332,342,345,363,368,370,372,428,503,526,535,546 'conversation.conversationid':305 'conversation.conversationwith':61,64,70,73,649 'conversation.lastmessage':347 'conversationid':176,431,636 'conversationindexmap':456 'conversations_bloc.dart':89 'conversations_event.dart':99 'conversations_state.dart':103 'conversationsbloc':22,585,596,614 'conversationsbloc.gettypingnotifier':304 'conversationsempti':143 'conversationserror':147 'conversationsiniti':127 'conversationsinitial/loading/loaded/empty/error':104 'conversationsload':24,132,136,153,420 'conversationsrequest':402 'conversationsrequestbuild':405,406 'conversationsserviceloc':25,115 'conversationsservicelocator.instance.deleteconversationusecase':604 'conversationsservicelocator.instance.getconversationusecase':600 'conversationsservicelocator.instance.getloggedinuserusecase':598 'conversationsservicelocator.instance.markasdeliveredusecase':602 'conversationsservicelocator.instance.setup':593 'conversationsst':23,615 'conversationsstyl':375 'conversationtyp':409 'correct':542,586,627 'count':31 'counter':159,424 'creat':578 'custom':293,320,401,667 'dart':55,126,290,323,403,521,576,605 'data':109,130,138,231 'datasourc':111 'delet':203 'deleteconvers':101,198 'deleteconversationoptionvis':387,685 'deleteconversationusecas':107,603 'di':114 'display':42 'domain/usecases':105 'e.g':212 'emiss':163,452 'empty/error/loading':122 'entir':326,510,608 'equal':442 'error':148 'etc':102,108 'even':164,443 'event':178,179,277 'exist':146,188,482 'expect':538 'extract':543,645 'fals':390,392 'featur':244 'fetch':133 'final':59,68,549,558,583,594 'first':588 'flutter':3,14,36 'foreground':491 'formatt':394,675 'getconversationsusecas':106 'getconversationusecas':599 'getloggedinuserusecas':597 'gotcha':419 'group':69,72,75,84,85,214,276,541,559,562,565,574,575,647 'guarante':161 'hasmor':140 'haven':168 'hideappbar':389 'icon':275 'id':167,199,205,217,224,233,440 'implement':8 'import':418 'increas':157 'indic':33,262,287,499,612,661 'initi':134,183,496,581 'isloadingmor':142 'isol':299 'item':119,295,328,622,631,641,669 'join/leave/kick/ban':279 'keep':187,470,480 'key':177 'kick':215 'last':255 'lastmessag':445 'limit':407 'list':11,27,44,118,135,189,208,267,294,302,327,437,466,483,511,609,633,668 'listen':91,250 'listitemview':329 'load':131,184,497 'loadconvers':100,181,479 'loadmoreconvers':193 'local':113 'lookup':98,460 'maintain':463 'map':473 'markasdeliveredusecas':601 'markdown':681 'markdowntextformatt':400 'materialpagerout':79,532,569 'member':278 'mention':19,679 'messag':150,240,253,256 'messagesscreen':81,534,537,571 'monoton':156 'move':257 'mycustomlistitem':331 'mytrailingwidget':371 'name':317 'navig':523 'navigator.push':77,530,567 'new':252 'note':357 'null':67,76,557,566 'o':96,458 'object':527 'one':515 'onitemtap':57,528,547,644 'online/offline':270 'open':222 'option':151 'outsid':467 'overrid':374 'pagin':194 'parallel':472 'pass':676 'pattern':289,520 'per':264,502,630 'per-convers':263 'perform':475 'person':516 'presenc':269 'prevent':508 'preview':397,684 'previousconvers':152 'purpos':180 'raw':525 'read':239 'read/delivery':272 'real':50,93,242,657 'real-tim':49,92,241,656 'rebuild':300,513,607,620,642 'receipt':273,274 'receiptsvis':385 'recent':28,46 'reconnect':282,493 'refresh':192,285,486 'regist':248 'remot':112 'remov':206 'removeconvers':204 'replac':325,333,349 'repositori':110 'requir':654,690 'resetunreadcount':232 'return':312,314 'scenario':494 'scroll':195 'sdk':90,171,201,210,249,426 'selectedconvers':141 'serviceloc':582 'set':651,686 'setactiveconvers':216 'setup':587 'showbackbutton':391 'silent':182,185,284,476 'singl':354 'single-arg':353 'singleton':116 'skill' 'skill-cometchat-flutter-v6-conversations' 'skip':450 'slot':322 'source-cometchat' 'specif':228 'state':124,281,506,618,625,666 'status':271 'style':373 'subscriptiontyp':650 'subtitl':120,336,396,683 'subtitleview':343 'subtitleview/leadingview/titleview':360 'tag':417 'take':361,366 'text':315,346,348,393,674 'textformatt':398 'time':51,94,243,351,658 'titlestyl':379 'top':260 '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' 'track':218 'trail':121,350 'trailingview':365,369 'trigger':17,283 'true':186,384,386,388,416,477 'two':338,436 'two-arg':337 'type':32,261,286,319,498,517,611,660 'typinglist':308,638 'typinglist.first.sender':316 'typinglist.isempty':311 'typography.heading3':380 'uikit':15,37 'uikitset':653 'uniqu':162 'unread':30,235 'unreadmessagecount':447 'updat':52,95,226,254,659 'updateconvers':223 'usag':54 'use':6,154,296,421,455,487,500,628,662,670,678 'user':60,63,66,82,83,238,268,410,539,550,553,556,572,573,646 'user/group':544 'usersstatusvis':383 'v6':4,16 'valuelisten':303,634 'valuelistenablebuild':297,301,629,632,663 'valuenotifi':266,288,501 'version':158,423 'view':123,321 'visibl':190,484 'widget':41,117,589 'without':209,432,580 'withtag':415 'wrong':522,577,606","prices":[{"id":"6b6bfa53-82ed-4776-9f03-2ec25993b3b6","listingId":"6e430f20-6c22-4f56-8e60-729d3fe4180b","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:10.841Z"}],"sources":[{"listingId":"6e430f20-6c22-4f56-8e60-729d3fe4180b","source":"github","sourceId":"cometchat/cometchat-skills/cometchat-flutter-v6-conversations","sourceUrl":"https://github.com/cometchat/cometchat-skills/tree/main/skills/cometchat-flutter-v6-conversations","isPrimary":false,"firstSeenAt":"2026-05-07T13:05:10.841Z","lastSeenAt":"2026-05-18T19:04:51.181Z"}],"details":{"listingId":"6e430f20-6c22-4f56-8e60-729d3fe4180b","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"cometchat","slug":"cometchat-flutter-v6-conversations","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":"b5ab87b46766b8a591f442832add212af2fa9d36","skill_md_path":"skills/cometchat-flutter-v6-conversations/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/cometchat/cometchat-skills/tree/main/skills/cometchat-flutter-v6-conversations"},"layout":"multi","source":"github","category":"cometchat-skills","frontmatter":{"name":"cometchat-flutter-v6-conversations","license":"MIT","description":"Use when implementing the conversations list with CometChat Flutter UIKit v6. Triggers on mentions of CometChatConversations, ConversationsBloc, ConversationsState, ConversationsLoaded, ConversationsServiceLocator, conversation list, recent chats, unread count, typing indicator in conversations, conversation item, last message, delete conversation, swipe actions, ConversationsRequestBuilder, onItemTap, subtitleView, trailingView, listItemView, or customizing the conversations screen. Also use when the user asks about showing a list of chats or recent conversations.","compatibility":"cometchat_chat_uikit ^6.0.0-beta2; flutter_bloc ^8.1.0"},"skills_sh_url":"https://skills.sh/cometchat/cometchat-skills/cometchat-flutter-v6-conversations"},"updatedAt":"2026-05-18T19:04:51.181Z"}}