cometchat-flutter-v6-conversations

$npx mdskill add cometchat/cometchat-skills/cometchat-flutter-v6-conversations

Renders a real-time conversations list using CometChat Flutter UIKit v6.

  • Displays recent chats with unread count and typing indicators.
  • Depends on cometchat_chat_uikit ^6.0.1 and flutter_bloc ^8.1.0.
  • Uses CometChatConversations widget and ConversationsBloc for state management.
  • Returns a customizable list view with swipe actions and item tap handling.

SKILL.md

.github/skills/cometchat-flutter-v6-conversationsView on GitHub ↗
---
name: cometchat-flutter-v6-conversations
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.
license: "MIT"
compatibility: "cometchat_chat_uikit ^6.0.1; flutter_bloc ^8.1.0"
metadata:
  author: "CometChat"
  version: "3.0.0"
  tags: "cometchat flutter conversations chat-list recent-chats bloc"
---

> **Ground truth:** `cometchat_chat_uikit: ^6.0` — pub-cache source + `ui-kit/flutter`. **Official docs:** https://www.cometchat.com/docs/ui-kit/flutter/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.

# CometChat Flutter UIKit — Conversations

The `CometChatConversations` widget displays a list of recent conversations with real-time updates.

## Basic Usage

```dart
CometChatConversations(
  onItemTap: (conversation) {
    final user = conversation.conversationWith is User
        ? conversation.conversationWith as User : null;
    final group = conversation.conversationWith is Group
        ? conversation.conversationWith as Group : null;
    Navigator.push(context, MaterialPageRoute(
      builder: (_) => MessagesScreen(user: user, group: group),
    ));
  },
)
```

## Architecture

```
conversations/                     # Key folders (excerpt)
├── bloc/
│   ├── conversations_bloc.dart    # SDK listeners, real-time updates, O(1) lookups
│   ├── conversations_event.dart   # LoadConversations, DeleteConversation, etc.
│   └── conversations_state.dart   # ConversationsInitial/Loading/Loaded/Empty/Error
├── domain/usecases/               # GetConversationsUseCase, DeleteConversationUseCase, etc.
├── data/                          # Repository + DataSources (remote + local)
├── di/                            # ConversationsServiceLocator (singleton)
├── utils/                         # Helpers (date formatting, last-message preview, etc.)
├── widgets/                       # List item, subtitle, trailing, empty/error/loading views
├── cometchat_conversations.dart   # Public widget entry point
└── cometchat_conversations_style.dart  # Style class
```

## State Classes

```dart
ConversationsInitial    // Before any data loaded
ConversationsLoading    // Fetching initial list
ConversationsLoaded     // Has data: conversations, hasMore, selectedConversations, isLoadingMore
ConversationsEmpty      // No conversations exist
ConversationsError      // Error with message + optional previousConversations
```

`ConversationsLoaded` uses a monotonically increasing `_version` counter to guarantee unique emissions even when conversation IDs haven't changed (SDK's `Conversation.==` only compares `conversationId`).

## Key Events

| Event | Purpose |
|-------|---------|
| `LoadConversations({silent})` | Initial load. `silent: true` keeps existing list visible during refresh. |
| `LoadMoreConversations` | Pagination (scroll to bottom) |
| `DeleteConversation(id)` | Calls SDK to delete |
| `RemoveConversation(id)` | Remove from list without SDK call (e.g., after group kick) |
| `SetActiveConversation(id)` | Track which conversation is open |
| `UpdateConversation({required conversationId, required updatedConversation, forceUpdate})` | Update a specific conversation's data. `forceUpdate` defaults to `true`. **Named params — positional form will not compile.** |
| `ResetUnreadCount(id)` | Clear unread badge when user reads messages |
| `RefreshConversations` | Re-fetch the list from the SDK (e.g., after a manual refresh gesture) |
| `ToggleConversationSelection(id)` | Add/remove a conversation from the multi-select set |
| `ClearConversationSelection` | Exit multi-select mode and clear selection |

## Real-Time Features

The BLoC automatically registers SDK listeners for:
- New messages → updates last message + moves conversation to top
- Typing indicators → per-conversation `ValueNotifier<List<TypingIndicator>>`
- User presence → online/offline status
- Read/delivery receipts → receipt icons
- Group events → member join/leave/kick/ban
- Connection state → reconnect triggers silent refresh

### Typing Indicators (ValueNotifier Pattern)

```dart
// In your custom list item, use ValueListenableBuilder for isolated rebuilds:
ValueListenableBuilder<List<TypingIndicator>>(
  valueListenable: conversationsBloc.getTypingNotifier(conversation.conversationId!),
  builder: (context, typingList, child) {
    if (typingList.isEmpty) return _buildLastMessage();
    return Text('${typingList.first.sender?.name} is typing...');
  },
)
```

## Customization — View Slots

```dart
CometChatConversations(
  // Replace entire list item
  listItemView: (conversation) => MyCustomListItem(conversation),

  // Replace just the subtitle — two-arg builder (BuildContext, Conversation)
  subtitleView: (context, conversation) => Text(conversation.lastMessage?.text ?? ''),

  // Replace trailing (time + badge) — SINGLE-arg builder. Note the asymmetry:
  // subtitleView/leadingView/titleView take (BuildContext, Conversation),
  // but trailingView takes just (Conversation).
  trailingView: (conversation) => MyTrailingWidget(conversation),

  // Style overrides
  conversationsStyle: CometChatConversationsStyle(
    backgroundColor: colors.background1,
    titleTextStyle: typography.heading3?.bold,
  ),

  // Configuration (all bool? — defaults supplied if null)
  usersStatusVisibility: true,
  receiptsVisibility: true,
  deleteConversationOptionVisibility: true,
  hideAppbar: false,
  showBackButton: true,

  // Callbacks — onBack is REQUIRED when showBackButton: true, otherwise the button is a no-op
  onBack: () => Navigator.of(context).maybePop(),

  // Error callback — OnError typedef is `Function(Exception e)`
  // Pair with hideError / errorStateView if you want to suppress or replace the in-widget error view.
  onError: (Exception e) => ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(content: Text('Failed to load conversations: $e')),
  ),

  // Other widget callbacks: onItemLongPress, onSelection, onEmpty, onLoad

  // Text formatters for subtitle preview
  textFormatters: [
    CometChatMentionsFormatter(),
    MarkdownTextFormatter(),
  ],
)
```

## Custom ConversationsRequest

```dart
CometChatConversations(
  conversationsRequestBuilder: ConversationsRequestBuilder()
    ..limit = 30
    ..conversationType = 'user'  // Only 1:1 chats
    ..withTags = true
    ..tags = ['important'],
)
```

## Gotchas

- `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.
- The BLoC uses `_conversationIndexMap` for O(1) lookups. If you maintain your own list outside the BLoC, keep a parallel Map for performance.
- `silent: true` on `LoadConversations` keeps the existing list visible during refresh — use this for background→foreground and reconnect scenarios, not initial load.
- Typing indicators use `ValueNotifier` per conversation, NOT BLoC state. This prevents the entire list from rebuilding when one person types.

## Anti-Patterns

```dart
// ❌ WRONG — navigating with raw conversation object
onItemTap: (conv) => Navigator.push(context, MaterialPageRoute(
  builder: (_) => MessagesScreen(conversation: conv), // MessagesScreen expects user OR group
))

// ✅ CORRECT — extract user/group from conversation
onItemTap: (conv) {
  final user = conv.conversationWith is User ? conv.conversationWith as User : null;
  final group = conv.conversationWith is Group ? conv.conversationWith as Group : null;
  Navigator.push(context, MaterialPageRoute(
    builder: (_) => MessagesScreen(user: user, group: group),
  ));
}
```

```dart
// ⚠️ The ServiceLocator is auto-initialized on first use, but calling
// setup() explicitly in main.dart before runApp() is the documented contract
// and surfaces any init failures early.

// ✅ RECOMMENDED — explicit setup, then construct
ConversationsServiceLocator.instance.setup();
final bloc = ConversationsBloc(
  // Minimal example — the constructor also accepts:
  //   getConversationsUseCase, loadMoreConversationsUseCase,
  //   disableSDKListeners, conversationsRequestBuilder,
  //   usersStatusVisibility, receiptsVisibility, includeBlockedUsers,
  //   visibleItemThreshold, disableSoundForMessages, customSoundForMessages,
  //   conversationsProtocol — all optional.
  getLoggedInUserUseCase: ConversationsServiceLocator.instance.getLoggedInUserUseCase,
  getConversationUseCase: ConversationsServiceLocator.instance.getConversationUseCase,
  markAsDeliveredUseCase: ConversationsServiceLocator.instance.markAsDeliveredUseCase,
  deleteConversationUseCase: ConversationsServiceLocator.instance.deleteConversationUseCase,
);
```

```dart
// ❌ WRONG — rebuilding entire list for typing indicator
BlocBuilder<ConversationsBloc, ConversationsState>(
  builder: (context, state) {
    // This rebuilds ALL items when ANY state changes
  },
)

// ✅ CORRECT — use ValueListenableBuilder per item
ValueListenableBuilder<List<TypingIndicator>>(
  valueListenable: bloc.getTypingNotifier(conversationId),
  builder: (_, typingList, __) { /* Only this item rebuilds */ },
)
```

## Checklist

- [ ] `onItemTap` extracts `User`/`Group` from `conversation.conversationWith`
- [ ] `onBack` wired when `showBackButton: true` (otherwise the back button is a no-op)
- [ ] `onError` callback (or `errorStateView` / `hideError`) wired so widget errors surface to users
- [ ] `subscriptionType` set in UIKitSettings (required for real-time updates)
- [ ] Typing indicators use `ValueListenableBuilder`, not BLoC state
- [ ] Custom list items use `CometChatThemeHelper` for colors
- [ ] Text formatters passed if using mentions or markdown in subtitle preview
- [ ] `deleteConversationOptionVisibility` set based on app requirements
- [ ] `UpdateConversation` dispatched with **named** params (`conversationId`, `updatedConversation`) — positional form will not compile

More from cometchat/cometchat-skills

SkillDescription
cometchatEntry-point for CometChat integration in any React, React Native, Angular, Android, Flutter, or iOS project — web (React/Next.js/React Router/Astro), React Native (Expo/bare), Angular (12-15), native Android (V6 stable, V5 legacy), Flutter (V6 stable, V5 legacy), and native iOS (V5 stable). Detects the framework, gathers requirements through an interactive conversation, and writes production-quality integration code.
cometchat-a11yAccessibility (a11y) for CometChat UI Kit integrations across all families — React, React Native, Angular, Android (V5/V6), iOS, Flutter. Covers WCAG 2.1 AA targets, keyboard navigation in chat, screen reader announcements (live regions for new messages), color contrast, focus management on call screens, motion-reduction support, and the cross-family checks that catch the common production a11y bugs. Cross-family — applies wherever the agent is checking accessibility.
cometchat-android-v5Entry-point dispatcher for CometChat Android UI Kit v5. Detects project setup, understands what the dev is building, and routes to the right sub-skills.
cometchat-android-v5-callsCometChat Calls SDK v5 integration for native Android (V5 stable, Java + Kotlin Views). Covers SDK setup (Cloudsmith Maven, CallAppSettings, init), the dual-SDK ringing pattern (Chat SDK initiateCall + Calls SDK joinSession), session settings, event listeners, call logs, recording, screen sharing, picture-in-picture, foreground service for ongoing calls, VoIP push via FCM + ConnectionService, audio/video/participant controls, custom UI, and in-call chat. Use in standalone mode (calls is the product) or additive mode (calls layered on top of an existing CometChat Android v5 chat integration).
cometchat-android-v5-componentsComplete catalog of CometChat Android UI Kit v5 components. Reference before writing integration code — never invent component names.
cometchat-android-v5-coreFoundational rules for CometChat Android UI Kit v5. Initialization, login, UIKitSettings builder, dependency setup, and anti-patterns. Read this first.
cometchat-android-v5-customizationCustomize CometChat components beyond theming — custom message templates, DataSource decorators, event listeners, and custom view slots.
cometchat-android-v5-extensionsExtension architecture for CometChat Android — ExtensionsDataSource, decorators, built-in extensions, and how to create custom extensions.
cometchat-android-v5-featuresAdd features (calls, reactions, polls, file sharing, AI, etc.) to an already-integrated CometChat project. Routes to the right sub-flow based on feature type.
cometchat-android-v5-placementWhere to put CometChat in your Android app — Activity, Fragment, BottomSheet, Dialog, Tab, or embedded patterns.