Documentation Index
Fetch the complete documentation index at: https://cometchat-22654f5b-docs-android-v6-beta2.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
This guide walks you through building a custom CometChatTextFormatter that detects #hashtags in messages, highlights them, and shows a suggestion dropdown in the composer.
Prerequisites
Extend CometChatTextFormatter and set the tracking character to #:
import 'package:cometchat_chat_uikit/cometchat_chat_uikit.dart';
import 'package:flutter/material.dart';
class HashtagFormatter extends CometChatTextFormatter {
final List<String> _allHashtags = [
'flutter', 'dart', 'cometchat', 'uikit', 'mobile',
'android', 'ios', 'web', 'chat', 'messaging',
];
HashtagFormatter() : super(trackingCharacter: '#');
@override
void init() {
// Called when the formatter is initialized
}
}
Step 2: Implement Search
The search method is called whenever the user types after the tracking character. Filter your data and set the suggestion list:
@override
void search(String query) {
if (query.isEmpty) {
setSuggestionItems(_allHashtags
.map((tag) => SuggestionItem(
id: tag,
name: '#$tag',
leadingIcon: Icon(Icons.tag, size: 20),
))
.toList());
return;
}
final filtered = _allHashtags
.where((tag) => tag.toLowerCase().contains(query.toLowerCase()))
.map((tag) => SuggestionItem(
id: tag,
name: '#$tag',
leadingIcon: Icon(Icons.tag, size: 20),
))
.toList();
setSuggestionItems(filtered);
}
Step 3: Handle Suggestion Selection
When the user taps a suggestion, insert the hashtag into the composer:
@override
void onItemClick(SuggestionItem item, User? user, Group? group) {
// The base class handles inserting the text into the composer.
// You can add custom logic here, like tracking analytics.
super.onItemClick(item, user, group);
}
Override buildMessageBubbleSpan to apply styling to hashtags when they appear in sent/received messages:
@override
List<CometChatTextFormatterResult> getFormattedText(
String text,
BuildContext context,
BubbleAlignment alignment,
) {
final results = <CometChatTextFormatterResult>[];
final regex = RegExp(r'#\w+');
for (final match in regex.allMatches(text)) {
results.add(CometChatTextFormatterResult(
start: match.start,
end: match.end,
style: TextStyle(
color: alignment == BubbleAlignment.right
? Colors.white.withOpacity(0.8)
: Color(0xFF6851D6),
fontWeight: FontWeight.w600,
),
onTap: () {
// Handle hashtag tap — navigate to hashtag feed, etc.
debugPrint('Tapped hashtag: ${match.group(0)}');
},
));
}
return results;
}
Step 5: Pre-Send Hook (Optional)
Modify the message before it’s sent — for example, attach hashtag metadata:
@override
BaseMessage handlePreMessageSend(BaseMessage message) {
if (message is TextMessage) {
final regex = RegExp(r'#\w+');
final hashtags = regex
.allMatches(message.text)
.map((m) => m.group(0)!.substring(1))
.toList();
if (hashtags.isNotEmpty) {
message.metadata ??= {};
message.metadata!['hashtags'] = hashtags;
}
}
return message;
}
Pass your formatter to the components that should use it:
final hashtagFormatter = HashtagFormatter();
// On the message list (for rendering)
CometChatMessageList(
user: user,
textFormatters: [
CometChatMentionsFormatter(), // Keep default mentions
hashtagFormatter,
],
)
// On the composer (for input suggestions)
CometChatMessageComposer(
user: user,
textFormatters: [
CometChatMentionsFormatter(),
hashtagFormatter,
],
)
Complete Example
class HashtagFormatter extends CometChatTextFormatter {
final List<String> _allHashtags = [
'flutter', 'dart', 'cometchat', 'uikit', 'mobile',
];
HashtagFormatter() : super(trackingCharacter: '#');
@override
void init() {}
@override
void search(String query) {
final filtered = query.isEmpty
? _allHashtags
: _allHashtags.where(
(tag) => tag.toLowerCase().contains(query.toLowerCase()),
).toList();
setSuggestionItems(filtered
.map((tag) => SuggestionItem(
id: tag,
name: '#$tag',
leadingIcon: Icon(Icons.tag, size: 20),
))
.toList());
}
@override
List<CometChatTextFormatterResult> getFormattedText(
String text,
BuildContext context,
BubbleAlignment alignment,
) {
final results = <CometChatTextFormatterResult>[];
final regex = RegExp(r'#\w+');
for (final match in regex.allMatches(text)) {
results.add(CometChatTextFormatterResult(
start: match.start,
end: match.end,
style: TextStyle(
color: alignment == BubbleAlignment.right
? Colors.white.withOpacity(0.8)
: Color(0xFF6851D6),
fontWeight: FontWeight.w600,
),
));
}
return results;
}
@override
BaseMessage handlePreMessageSend(BaseMessage message) {
if (message is TextMessage) {
final hashtags = RegExp(r'#\w+')
.allMatches(message.text)
.map((m) => m.group(0)!.substring(1))
.toList();
if (hashtags.isNotEmpty) {
message.metadata ??= {};
message.metadata!['hashtags'] = hashtags;
}
}
return message;
}
}