cometchat-flutter-v6-conversations — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited cometchat-flutter-v6-conversations (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: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.
The CometChatConversations widget displays a list of recent conversations with real-time updates.
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),
));
},
)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 classConversationsInitial // Before any data loaded
ConversationsLoading // Fetching initial list
ConversationsLoaded // Has data: conversations, hasMore, selectedConversations, isLoadingMore
ConversationsEmpty // No conversations exist
ConversationsError // Error with message + optional previousConversationsConversationsLoaded uses a monotonically increasing _version counter to guarantee unique emissions even when conversation IDs haven't changed (SDK's Conversation.== only compares conversationId).
| 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 |
The BLoC automatically registers SDK listeners for:
ValueNotifier<List<TypingIndicator>>// 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...');
},
)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(),
],
)CometChatConversations(
conversationsRequestBuilder: ConversationsRequestBuilder()
..limit = 30
..conversationType = 'user' // Only 1:1 chats
..withTags = true
..tags = ['important'],
)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._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.ValueNotifier per conversation, NOT BLoC state. This prevents the entire list from rebuilding when one person types.// ❌ 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),
));
}// ⚠️ 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,
);// ❌ 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 */ },
)onItemTap extracts User/Group from conversation.conversationWithonBack wired when showBackButton: true (otherwise the back button is a no-op)onError callback (or errorStateView / hideError) wired so widget errors surface to userssubscriptionType set in UIKitSettings (required for real-time updates)ValueListenableBuilder, not BLoC stateCometChatThemeHelper for colorsdeleteConversationOptionVisibility set based on app requirementsUpdateConversation dispatched with named params (conversationId, updatedConversation) — positional form will not compile~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.