Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
cometchat avatar

Cometchat Flutter V6 Customization

  • 6 installs
  • 70 repo stars
  • Updated June 23, 2026
  • cometchat/cometchat-skills

Customize CometChat Flutter UIKit v6 beyond defaults with props/view slots, request builders, text formatters, message templates, and BubbleFactory/DataSource.

About

Presents a four-tier customization model for CometChat Flutter UIKit v6 from view slots to BubbleFactory. A developer uses it for custom bubbles, headers, list items, actions, or new message types.

  • Four tiers: view slots, request builders, formatters/templates, BubbleFactory
  • Start at Tier 1 and escalate only when lighter tiers fall short

Cometchat Flutter V6 Customization by the numbers

  • 6 all-time installs (skills.sh)
  • Ranked #806 of 1,039 Mobile Development skills by installs in the Skillselion catalog
  • Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/cometchat/cometchat-skills --skill cometchat-flutter-v6-customization

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs6
repo stars70
Last updatedJune 23, 2026
Repositorycometchat/cometchat-skills

What it does

Customize CometChat Flutter UIKit v6 beyond defaults with props/view slots, request builders, text formatters, message templates, and BubbleFactory/DataSource.

Files

SKILL.mdMarkdownGitHub ↗

CometChat Flutter UIKit v6 — Customization Guide

Four tiers of customization, from lightest to deepest.

1. Four-Tier Customization Model

TierMechanismScopeWhen to Use
1Props & View SlotsPer-component UI overridesCustom list items, subtitles, trailing widgets, headers
2Request BuildersData filtering & paginationFilter conversations, users, groups, messages, group members
3Text Formatters & Message TemplatesMessage rendering & actionsCustom text styling, custom long-press options, custom bubble slots
4BubbleFactory & DataSourceNew message typesLocation bubbles, poll bubbles, any custom category_type

Start at Tier 1. Move deeper only when the lighter tier can't solve the problem.

2. Tier 1: Props & View Slots

Every list-based component exposes view slot callbacks that let you replace individual parts of each list item without rebuilding the entire widget.

CometChatConversations

CometChatConversations(
  // Replace the entire list item
  listItemView: (Conversation conversation) => MyCustomConversationTile(conversation),

  // Replace individual slots
  subtitleView: (BuildContext context, Conversation conversation) =>
      Text(conversation.lastMessage?.text ?? ''),
  trailingView: (Conversation conversation) =>
      Icon(Icons.chevron_right),
  leadingView: (BuildContext context, Conversation conversation) =>
      CircleAvatar(child: Text(conversation.conversationWith?.name?[0] ?? '')),
  titleView: (BuildContext context, Conversation conversation) =>
      Text(conversation.conversationWith?.name ?? '', style: TextStyle(fontWeight: FontWeight.bold)),

  // State views
  emptyStateView: (context) => Center(child: Text('No conversations yet')),
  errorStateView: (context) => Center(child: Text('Something went wrong')),
  loadingStateView: (context) => Center(child: CircularProgressIndicator()),
)

CometChatUsers

CometChatUsers(
  listItemView: (User user) => MyCustomUserTile(user),
  subtitleView: (BuildContext context, User user) => Text(user.status ?? ''),
  trailingView: (BuildContext context, User user) => Icon(Icons.message),
  leadingView: (BuildContext context, User user) => CometChatAvatar(name: user.name),
  titleView: (BuildContext context, User user) => Text(user.name),
)

CometChatGroups

CometChatGroups(
  listItemView: (Group group) => MyCustomGroupTile(group),
  subtitleView: (BuildContext context, Group group) =>
      Text('${group.membersCount} members'),
  trailingView: (BuildContext context, Group group) => Icon(Icons.arrow_forward),
  leadingView: (BuildContext context, Group group) => CometChatAvatar(name: group.name),
  titleView: (BuildContext context, Group group) => Text(group.name),
)

CometChatMessageHeader

CometChatMessageHeader(
  user: user,
  group: group,
  subtitleView: (Group? group, User? user, BuildContext context) =>
      Text('Custom subtitle'),
  trailingView: (User? user, Group? group, BuildContext context) => [
    IconButton(icon: Icon(Icons.search), onPressed: () {}),
    IconButton(icon: Icon(Icons.info_outline), onPressed: () {}),
  ],
  listItemView: (Group? group, User? user, BuildContext context) =>
      MyCustomHeaderWidget(user: user, group: group),
  titleView: null, // use default
  leadingStateView: null, // use default
)

CometChatMessageList

CometChatMessageList(
  user: user,
  group: group,
  headerView: (context, state) => MyCustomListHeader(),
  footerView: (context, state) => MyCustomListFooter(),
  emptyStateView: (context) => Center(child: Text('Start a conversation')),
  emptyChatGreetingView: (context) => WelcomeWidget(),
  loadingStateView: (context) => ShimmerList(),
  errorStateView: (context) => RetryWidget(),
)

CometChatMessageComposer

CometChatMessageComposer(
  user: user,
  group: group,
  headerView: (context, state) => ReplyPreviewBanner(),
  footerView: (context, state) => SuggestedActionsBar(),
  auxiliaryButtonView: (context, user, group, composerState) =>
      IconButton(icon: Icon(Icons.gif), onPressed: () {}),
  secondaryButtonView: (context, user, group, composerState) =>
      IconButton(icon: Icon(Icons.attach_file), onPressed: () {}),
  sendButtonView: Icon(Icons.send, color: Colors.blue),
)

3. Tier 2: Request Builders

Override the SDK request builder to control what data is fetched.

ConversationsRequestBuilder

CometChatConversations(
  conversationsRequestBuilder: ConversationsRequestBuilder()
    ..limit = 30
    ..conversationType = ConversationType.user // only 1-on-1 chats
    ..withTags = true
    ..tags = ['vip'],
)

MessagesRequestBuilder

CometChatMessageList(
  user: user,
  messagesRequestBuilder: MessagesRequestBuilder()
    ..uid = user.uid
    ..limit = 50
    ..hideDeletedMessages = true
    ..searchKeyword = 'invoice'
    ..categories = [MessageCategoryConstants.message]
    ..types = [MessageTypeConstants.text, MessageTypeConstants.image],
)

UsersRequestBuilder

CometChatUsers(
  usersRequestBuilder: UsersRequestBuilder()
    ..limit = 30
    ..friendsOnly = true
    ..searchKeyword = 'john'
    ..roles = ['admin', 'moderator'],
)

GroupsRequestBuilder

CometChatGroups(
  groupsRequestBuilder: GroupsRequestBuilder()
    ..limit = 30
    ..joinedOnly = true
    ..searchKeyword = 'team'
    ..withTags = true
    ..tags = ['project-alpha'],
)

GroupMembersRequestBuilder

CometChatGroupMembers(
  group: group,
  groupMembersRequestBuilder: GroupMembersRequestBuilder(group.guid)
    ..limit = 30
    ..scopes = [GroupMemberScope.admin, GroupMemberScope.moderator],
)

4. Tier 3: Text Formatters & Message Templates

Text Formatters

CometChatTextFormatter is the abstract base class. Subclass it to create custom text styling in both the message list and the composer.

Built-in formatters:

FormatterPurpose
CometChatMentionsFormatter@mention users with suggestion list
MarkdownTextFormatterBold, italic, strikethrough, code, links, lists
CometChatUrlFormatterClickable URLs
CometChatPhoneNumberFormatterClickable phone numbers
CometChatEmailFormatterClickable email addresses

Key properties on CometChatTextFormatter:

abstract class CometChatTextFormatter implements Formatter {
  String? trackingCharacter;       // e.g. '@' for mentions
  RegExp? pattern;                 // regex to match in text
  Function(String?)? onSearch;     // called when tracking character typed
  bool? showLoadingIndicator;
  BaseMessage? message;
  User? user;
  Group? group;
  StreamSink<List<SuggestionListItem>>? suggestionListEventSink;

  void init();
  void handlePreMessageSend(BuildContext context, BaseMessage baseMessage);
  void onScrollToBottom(TextEditingController textEditingController);
  void onChange(TextEditingController textEditingController, String previousText);

  List<AttributedText> buildInputFieldText({...});
  List<AttributedText> getAttributedText(String text, BuildContext context, BubbleAlignment? alignment, {...});
  TextStyle getMessageBubbleTextStyle(BuildContext context, BubbleAlignment? alignment, {bool forConversation = false});
  TextStyle getMessageInputTextStyle(BuildContext context);
}

Pass the same formatters to both list and composer:

final formatters = [
  CometChatMentionsFormatter(user: user, group: group),
  MarkdownTextFormatter(),
  CometChatUrlFormatter(),
  CometChatPhoneNumberFormatter(),
  CometChatEmailFormatter(),
];

CometChatMessageList(user: user, textFormatters: formatters)
CometChatMessageComposer(user: user, textFormatters: formatters)

Message Templates

CometChatMessageTemplate controls how a message type renders in the bubble and what long-press options appear.

class CometChatMessageTemplate {
  CometChatMessageTemplate({
    required this.type,       // e.g. 'text', 'image', or 'location'
    required this.category,   // e.g. 'message' or 'custom'
    this.bubbleView,          // replaces the ENTIRE bubble
    this.headerView,          // top of bubble (sender name area)
    this.contentView,         // main content area
    this.footerView,          // below statusInfoView
    this.bottomView,          // below contentView
    this.statusInfoView,      // receipts/time area
    this.threadView,          // thread reply indicator
    this.replyView,           // quoted reply preview
    this.options,             // long-press menu options
  });
}

Override templates on CometChatMessageList:

CometChatMessageList(
  user: user,
  // Replace all templates
  templates: [
    CometChatMessageTemplate(
      type: MessageTypeConstants.text,
      category: MessageCategoryConstants.message,
      contentView: (message, context, alignment, {additionalConfigurations}) =>
          MyCustomTextContent(message: message),
      options: (loggedInUser, message, context, group, additionalConfigurations) => [
        CometChatMessageOption(
          id: 'bookmark',
          title: 'Bookmark',
          icon: Icon(Icons.bookmark_border, size: 24),
          onItemClick: (message, state) {
            // handle bookmark
          },
        ),
      ],
    ),
  ],
  // Or add templates alongside defaults
  addTemplate: [
    CometChatMessageTemplate(
      type: 'location',
      category: 'custom',
      contentView: (message, context, alignment, {additionalConfigurations}) =>
          LocationBubbleContent(message: message as CustomMessage),
    ),
  ],
)

CometChatMessageOption model:

CometChatMessageOption(
  id: 'pin',                    // unique identifier
  title: 'Pin Message',         // display text
  icon: Icon(Icons.push_pin),   // leading icon
  onItemClick: (BaseMessage message, CometChatMessageListControllerProtocol state) {
    // your action
  },
  messageOptionSheetStyle: CometChatMessageOptionSheetStyle(...),
)

5. Tier 4: BubbleFactory & DataSource

BubbleFactory

The deepest customization for rendering message content. Each factory handles one category_type key.

/// Abstract factory — one per message type.
abstract class BubbleFactory<T extends BaseMessage> {
  Widget build(
    BuildContext context,
    T message,
    BubbleAlignment alignment, {
    CometChatColorPalette? colorPalette,
    CometChatTypography? typography,
    CometChatSpacing? spacing,
  });

  /// Returns "category_type" key, or "deleted" for deleted messages.
  static String getFactoryKey(BaseMessage message);

  /// Creates a key from category + type strings.
  static String createKey(String category, String type) => '${category}_$type';
}

DefaultBubbleFactories

The built-in registry:

class DefaultBubbleFactories {
  static Map<String, BubbleFactory> getDefaults({
    List<CometChatTextFormatter>? textFormatters,
    CometChatTextBubbleStyle? incomingTextStyle,
    CometChatTextBubbleStyle? outgoingTextStyle,
    CometChatImageBubbleStyle? imageStyle,
    CometChatVideoBubbleStyle? videoStyle,
    CometChatAudioBubbleStyle? audioStyle,
    CometChatFileBubbleStyle? fileStyle,
  });
}

Default keys registered:

  • message_textTextBubbleFactory
  • message_imageImageBubbleFactory
  • message_videoVideoBubbleFactory
  • message_audioAudioBubbleFactory
  • message_fileFileBubbleFactory
  • deletedDeletedBubbleFactory

Creating a Custom BubbleFactory

Example: a location message bubble.

class LocationBubbleFactory extends BubbleFactory<CustomMessage> {
  @override
  Widget build(
    BuildContext context,
    CustomMessage message,
    BubbleAlignment alignment, {
    CometChatColorPalette? colorPalette,
    CometChatTypography? typography,
    CometChatSpacing? spacing,
  }) {
    final data = message.customData;
    final lat = data?['latitude'] as double? ?? 0;
    final lng = data?['longitude'] as double? ?? 0;

    return GestureDetector(
      onTap: () => _openMap(lat, lng),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Image.network(
            'https://maps.googleapis.com/maps/api/staticmap?center=$lat,$lng&zoom=15&size=300x200&key=YOUR_KEY',
            width: 240,
            height: 160,
            fit: BoxFit.cover,
          ),
          Padding(
            padding: EdgeInsets.all(spacing?.padding2 ?? 8),
            child: Text(
              '📍 $lat, $lng',
              style: typography?.body?.regular,
            ),
          ),
        ],
      ),
    );
  }
}

Registering Custom Factories

Merge your custom factories with the defaults using CometChatMessageTemplate.addTemplate on the message list, or by providing a custom templates list that includes a contentView for your custom type.

The CometChatMessageBubble widget supports two modes:

  • Smart mode: pass message and the factory registry resolves the content widget via BubbleFactory.getFactoryKey(message) → O(1) map lookup.
  • Manual mode: pass contentView directly — bypasses the factory.
// Using addTemplate to register a custom type alongside defaults
CometChatMessageList(
  user: user,
  addTemplate: [
    CometChatMessageTemplate(
      type: 'location',
      category: 'custom',
      contentView: (message, context, alignment, {additionalConfigurations}) {
        final factory = LocationBubbleFactory();
        return factory.build(context, message as CustomMessage, alignment);
      },
    ),
  ],
)

DataSource Pattern

Each component follows Clean Architecture with its own data source layer. The data sources abstract SDK calls behind interfaces:

// Example: ConversationsRemoteDataSource
abstract class ConversationsRemoteDataSource {
  Future<List<Conversation>> getConversations({ConversationsRequest? request});
  Future<void> deleteConversation(String conversationWith);
}

class ConversationsRemoteDataSourceImpl implements ConversationsRemoteDataSource {
  // Delegates to CometChat SDK
}

To customize data fetching, provide a custom BLoC instance:

CometChatConversations(
  conversationsBloc: MyCustomConversationsBloc(),
)

CometChatUsers(
  usersBloc: MyCustomUsersBloc(),
)

CometChatGroups(
  groupsBloc: MyCustomGroupsBloc(),
)

CometChatMessageList(
  messageListBloc: MyCustomMessageListBloc(),
)

6. Style Overrides

Every component has a CometChat{Component}Style class that extends ThemeExtension. Styles use a merge() pattern — your overrides layer on top of theme defaults.

Pattern

@immutable
class CometChatTextBubbleStyle extends ThemeExtension<CometChatTextBubbleStyle> {
  const CometChatTextBubbleStyle({
    this.textStyle,
    this.textColor,
    this.backgroundColor,
    this.border,
    this.borderRadius,
    this.messageBubbleAvatarStyle,
    this.messageBubbleDateStyle,
    this.messageBubbleBackgroundImage,
    this.senderNameTextStyle,
    this.messageReceiptStyle,
    // ...
  });

  // Factory to get theme-registered instance
  static CometChatTextBubbleStyle of(BuildContext context) => const CometChatTextBubbleStyle();

  // Merge your overrides on top of theme defaults
  CometChatTextBubbleStyle merge(CometChatTextBubbleStyle? style);

  // copyWith for selective overrides
  CometChatTextBubbleStyle copyWith({...});
}

Usage

CometChatConversations(
  conversationsStyle: CometChatConversationsStyle(
    backgroundColor: Colors.grey[100],
    titleTextStyle: TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
  ),
)

CometChatMessageHeader(
  user: user,
  messageHeaderStyle: CometChatMessageHeaderStyle(
    backgroundColor: colorPalette.background1,
  ),
)

CometChatMessageList(
  user: user,
  style: CometChatMessageListStyle(
    backgroundColor: Colors.white,
  ),
)

CometChatMessageComposer(
  user: user,
  messageComposerStyle: CometChatMessageComposerStyle(
    backgroundColor: Colors.white,
    borderRadius: BorderRadius.circular(24),
  ),
)

Theme Caching

For performance, parent widgets cache theme lookups in didChangeDependencies() and pass them to children via optional colorPalette, spacing, typography params. This avoids expensive CometChatThemeHelper lookups during keyboard animation rebuilds.

// Parent caches once
late CometChatColorPalette _colorPalette;
bool _themeInitialized = false;

@override
void didChangeDependencies() {
  super.didChangeDependencies();
  if (!_themeInitialized) {
    _colorPalette = CometChatThemeHelper.getColorPalette(context);
    _themeInitialized = true;
  }
}

// Pass to children
CometChatMessageBubble(
  colorPalette: _colorPalette,  // pre-cached, zero lookups in child
  spacing: _spacing,
)

7. Anti-Patterns

// ❌ WRONG — different formatters for list and composer
CometChatMessageList(textFormatters: [MarkdownTextFormatter()])
CometChatMessageComposer(textFormatters: []) // inconsistent rendering

// ✅ CORRECT — same formatter list
final formatters = [CometChatMentionsFormatter(user: user), MarkdownTextFormatter()];
CometChatMessageList(textFormatters: formatters)
CometChatMessageComposer(textFormatters: formatters)
// ❌ WRONG — calling CometChatThemeHelper in build() of a frequently-rebuilt widget
@override
Widget build(BuildContext context) {
  final colorPalette = CometChatThemeHelper.getColorPalette(context); // expensive every rebuild
  return Container(color: colorPalette.primary);
}

// ✅ CORRECT — cache in didChangeDependencies, use _themeInitialized flag
// ❌ WRONG — overriding templates without providing options (loses default long-press menu)
CometChatMessageList(
  templates: [
    CometChatMessageTemplate(
      type: MessageTypeConstants.text,
      category: MessageCategoryConstants.message,
      contentView: (msg, ctx, align, {additionalConfigurations}) => Text(msg.text),
      // options: null — no long-press menu at all!
    ),
  ],
)

// ✅ CORRECT — use addTemplate to add new types, or include options when overriding templates
// ❌ WRONG — creating a BubbleFactory that ignores the colorPalette/spacing params
class BadFactory extends BubbleFactory<CustomMessage> {
  @override
  Widget build(BuildContext context, CustomMessage message, BubbleAlignment alignment, {
    CometChatColorPalette? colorPalette,
    CometChatTypography? typography,
    CometChatSpacing? spacing,
  }) {
    return Container(color: Colors.blue); // hardcoded color, ignores theme
  }
}

// ✅ CORRECT — use the passed theme values
return Container(color: colorPalette?.primary ?? Colors.blue);
// ❌ WRONG — forgetting resizeToAvoidBottomInset: false on Scaffold with composer
Scaffold(
  body: Column(children: [
    Expanded(child: CometChatMessageList(user: user)),
    CometChatMessageComposer(user: user),
  ]),
)

// ✅ CORRECT
Scaffold(
  resizeToAvoidBottomInset: false,
  body: Column(children: [
    Expanded(child: CometChatMessageList(user: user)),
    CometChatMessageComposer(user: user),
  ]),
)

8. Checklist

  • [ ] Start at Tier 1 (props/view slots) before going deeper
  • [ ] Same textFormatters list passed to both CometChatMessageList and CometChatMessageComposer
  • [ ] Use addTemplate to add new message types alongside defaults (don't replace templates unless intentional)
  • [ ] Custom BubbleFactory.build() uses the passed colorPalette/typography/spacing params, not hardcoded values
  • [ ] Style overrides use merge() pattern, not constructor replacement
  • [ ] Theme lookups cached in didChangeDependencies() with _themeInitialized flag
  • [ ] Scaffold containing CometChatMessageComposer has resizeToAvoidBottomInset: false
  • [ ] Custom CometChatMessageOption.onItemClick handles both BaseMessage and the controller protocol
  • [ ] Request builders set limit to a reasonable value (default 30–50)
  • [ ] Mutable _user/_group state copies passed to UIKit components, not widget.user/widget.group

Related skills

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.