Skip to main content
Each component’s BLoC manages data fetching, state transitions, and business logic. You can configure it via RequestBuilders, provide a custom BLoC instance, or extend the default one.

Configuring Data Fetching with RequestBuilders

Use request builders to control what data the component fetches. Pass the builder instance — not the result of .build().
CometChatMessageList(
  user: user,
  messagesRequestBuilder: MessagesRequestBuilder()
    ..uid = user.uid
    ..searchKeyword = 'hello'
    ..limit = 30,
)
The following parameters in MessagesRequestBuilder will always be altered inside the message list: UID, GUID, types, categories.

Request Builder by Component

ComponentBuilder PropertyBuilder Type
CometChatConversationsconversationsRequestBuilderConversationsRequestBuilder
CometChatUsersusersRequestBuilderUsersRequestBuilder
CometChatGroupsgroupsRequestBuilderGroupsRequestBuilder
CometChatGroupMembersgroupMembersRequestBuilderGroupMembersRequestBuilder
CometChatMessageListmessagesRequestBuilderMessagesRequestBuilder

Providing a Custom BLoC

Each component accepts an optional BLoC parameter. Provide your own instance to override default behavior:
final myBloc = MessageListBloc(
  user: user,
  parentMessageId: null,
  types: MessageTemplateUtils.getAllMessageTypes(),
  categories: MessageTemplateUtils.getAllMessageCategories(),
);

CometChatMessageList(
  user: user,
  messageListBloc: myBloc,
)

BLoC Parameters by Component

ComponentBLoC PropertyBLoC Type
CometChatConversationsconversationsBlocConversationsBloc
CometChatMessageListmessageListBlocMessageListBloc
CometChatMessageComposermessageComposerBlocMessageComposerBloc

Extending the Default BLoC

Extend the default BLoC class and override hooks to add custom behavior:
class CustomMessageListBloc extends MessageListBloc {
  CustomMessageListBloc({required User user})
      : super(user: user);

  @override
  void onItemAdded(BaseMessage item, List<BaseMessage> updatedList) {
    // Custom logic when a message is added
    debugPrint('Message added: ${item.id}');
    super.onItemAdded(item, updatedList);
  }

  @override
  void onItemRemoved(BaseMessage item, List<BaseMessage> updatedList) {
    // Custom logic when a message is removed
    super.onItemRemoved(item, updatedList);
  }

  @override
  void onItemUpdated(BaseMessage oldItem, BaseMessage newItem, List<BaseMessage> updatedList) {
    // Custom logic when a message is updated
    super.onItemUpdated(oldItem, newItem, updatedList);
  }
}

ListBase Hooks

All list-based BLoCs use the ListBase mixin with these override hooks:
HookCalled When
onItemAdded(item, updatedList)An item is added to the list
onItemRemoved(item, updatedList)An item is removed from the list
onItemUpdated(oldItem, newItem, updatedList)An item is updated in the list
onListCleared(previousList)The list is cleared
onListReplaced(previousList, newList)The entire list is replaced

Component-Specific BLoC Events

Each component’s BLoC has its own events and methods. See the individual component docs for details:

Lifecycle Callbacks

CometChatMessageList(
  user: user,
  onLoad: (messages) {
    debugPrint('Loaded ${messages.length} messages');
  },
  onEmpty: () {
    debugPrint('No messages found');
  },
  onError: (e) {
    debugPrint('Error: ${e.message}');
  },
)

Repository & Datasource Overrides

Both CometChatConversations and CometChatMessageList follow the same clean architecture stack. There are two override points:
Widget → BLoC → UseCase → Repository → DataSource (Remote + Local)
                  ↑               ↑
          override here    or here

1. Datasource Override

Implement the abstract datasource interfaces to swap the data layer entirely — e.g. a REST API instead of the CometChat SDK, or a persistent cache instead of in-memory.

Conversations

abstract class ConversationsRemoteDataSource {
  Future<List<Conversation>> getConversations({int limit, String? fromId});
  Future<Conversation> getConversation(String conversationId);
  Future<void> deleteConversation(String conversationWith, String conversationType);
  Future<void> markMessageAsRead(BaseMessage message);
  Future<Conversation> updateConversation(Conversation conversation);
}

abstract class ConversationsLocalDataSource {
  Future<void> cacheConversations(List<Conversation> conversations);
  Future<List<Conversation>> getCachedConversations();
  Future<void> cacheConversation(Conversation conversation);
  Future<Conversation?> getCachedConversation(String conversationId);
  Future<void> removeCachedConversation(String conversationId);
  Future<void> clearCache();
  Future<bool> hasCachedData();
}

Message List

abstract class MessageListRemoteDataSource {
  Future<GetMessagesResult> getMessages({...});
  Future<List<BaseMessage>> fetchPreviousMessages({required MessagesRequest request});
  Future<List<BaseMessage>> fetchNextMessages({required MessagesRequest request});
  Future<void> markAsRead(BaseMessage message);
  Future<void> markAsDelivered(BaseMessage message);
  Future<User?> getLoggedInUser();
  Future<Conversation> getConversation({...});
  Future<Conversation> markMessageAsUnread(BaseMessage message);
}

abstract class MessageListLocalDataSource {
  Future<void> cacheMessages(String conversationId, List<BaseMessage> messages);
  Future<List<BaseMessage>> getCachedMessages(String conversationId);
  // ... cache CRUD methods
}

2. Repository Override

The repository interfaces sit above the datasources. Override here to change business logic — caching strategy, error handling, retry logic — without touching the datasource layer.

Conversations

abstract class ConversationsRepository {
  Future<Result<List<Conversation>>> getConversations({int limit, String? fromId});
  Future<Result<Conversation>> getConversationById(String conversationId);
  Future<Result<void>> deleteConversation(String conversationId);
  Future<Result<Conversation>> updateConversation(Conversation conversation);
  Future<Result<User?>> getLoggedInUser();
  Future<Result<void>> markAsDelivered(BaseMessage message);
  Future<Result<Conversation>> getConversation({...});
}

Message List

abstract class MessageListRepository {
  Future<Result<List<BaseMessage>>> getMessages({...});
  Future<Result<List<BaseMessage>>> fetchPreviousMessages({...});
  Future<Result<List<BaseMessage>>> fetchNextMessages({...});
  Future<Result<void>> markAsRead(BaseMessage message);
  Future<Result<void>> markAsDelivered(BaseMessage message);
  Future<Result<User?>> getLoggedInUser();
  Future<Result<Conversation>> getConversation({...});
  Future<Result<Conversation>> markMessageAsUnread(BaseMessage message);
}

3. Wiring via the Service Locator

The service locators are the injection point. Both are singletons with a setup() method. Call reset() first, then setup() with your custom implementations before the widget mounts.

Datasource-level override (Conversations)

class MyConversationsRemoteDataSource implements ConversationsRemoteDataSource {
  @override
  Future<List<Conversation>> getConversations({int limit = 30, String? fromId}) async {
    // Your REST API call, mock data, etc.
  }
  // ... implement remaining methods
}

// Before mounting CometChatConversations:
await ConversationsServiceLocator.instance.reset();

final remote = MyConversationsRemoteDataSource();
final local = ConversationsLocalDataSourceImpl(); // keep default cache
final repo = ConversationsRepositoryImpl(
  remoteDataSource: remote,
  localDataSource: local,
);

Repository-level override (Message List)

class MyMessageListRepository implements MessageListRepository {
  @override
  Future<Result<List<BaseMessage>>> getMessages({...}) async {
    // Custom logic
  }
  // ...
}

// Before mounting CometChatMessageList:
await MessageListServiceLocator.instance.reset();
// Wire your repo into use cases and pass to the BLoC

Key Points

PointDetail
reset()Sets _isInitialized = false — always call before re-wiring
setup()Guarded by _isInitialized — safe to call multiple times normally
localDataSource in MessageListRepositoryImplOptional — pass null to disable caching entirely
Fallback cachingConversationsRepositoryImpl falls back to local cache when remote fails — your custom remote inherits this if you use ConversationsRepositoryImpl with a custom datasource
Result<T>All repository methods return Result<T> (Success/Failure) — your implementations must do the same