Sending Messages
WKIM.shared.messageManager.sendMessage(WKTextContent('I am a text message'), WKChannel('uid_1', WKChannelType.personal));
Text Messages
// Define text message
WKTextContent text = WKTextContent("Hello, WuKong");
// Send text message
WKIM.shared.messageManager.sendMessage(text, channel);
Image Messages
// Define image message
WKImageContent image = WKImageContent(100, 100);
image.localPath = "xxx"; // Image local path
image.url = "http://xxx.com/xxx.jpg"
// Send image message
WKIM.shared.messageManager.sendMessage(image, channel);
Custom Messages
Reference custom messages: Custom Messages
Message Storage Callback (Not Message Send Status)
When sending messages, the SDK will trigger a storage callback after saving the message to the local database. At this point, the message has not been sent yet, and you can display the message in the UI in this listener.
Listen for message storage events:
WKIM.shared.messageManager.addOnMsgInsertedListener((wkMsg) {
// Display in UI
});
New Messages
Listen for new message events:
// Listen for new message events
WKIM.shared.messageManager.addOnNewMsgListener('chat', (msgs) {
// Display in UI
});
// Remove new message listener
WKIM.shared.messageManager.removeNewMsgListener('chat');
If you receive new messages in a chat page, you need to determine whether the message belongs to the current conversation by checking the channelID and channelType of the message object WKMsg
Message Refresh Listening
When the SDK updates messages, such as: message send status, someone likes a message, message read receipt, message recall, message editing, etc., the SDK will callback the following event. The UI can determine which specific message has changed through the clientMsgNO of the message object WKMsg.
Listen for refresh message events:
// Listen for refresh message events
WKIM.shared.messageManager.addOnRefreshMsgListener('chat', (wkMsg) {
// TODO refresh message
});
// Remove refresh message listener
WKIM.shared.messageManager.removeOnRefreshMsgListener('chat');
/*
* Query or sync messages for a channel
*
* @param channelId Channel ID
* @param channelType Channel type
* @param oldestOrderSeq Last message's large orderSeq, pass 0 for first entry into chat
* @param contain Whether to include the oldestOrderSeq message
* @param pullMode Pull mode 0: pull down 1: pull up
* @param aroundMsgOrderSeq Query messages around this message, e.g. aroundMsgOrderSeq=20 returns [16,17,19,20,21,22,23,24,25]
* @param limit Number to get each time
* @param iGetOrSyncHistoryMsgBack Request callback
* @param syncBack Sync message callback, can show loading through this callback
*/
WKIM.shared.messageManager.getOrSyncHistoryMessages(
channelID, channelType, oldestOrderSeq, contain, pullMode, limit, aroundMsgOrderSeq, Function(List<WKMsg>)){
}, Function() syncBack);
Getting history messages is not a synchronous method, as there may be non-continuous data that needs to be synced from the server
Complete Message Management Example
class MessageManager {
static final MessageManager _instance = MessageManager._internal();
factory MessageManager() => _instance;
MessageManager._internal();
final Map<String, List<WKMsg>> _channelMessages = {};
final StreamController<List<WKMsg>> _newMessagesController = StreamController.broadcast();
final StreamController<WKMsg> _messageUpdateController = StreamController.broadcast();
// Streams for UI to listen
Stream<List<WKMsg>> get newMessagesStream => _newMessagesController.stream;
Stream<WKMsg> get messageUpdateStream => _messageUpdateController.stream;
void initialize() {
_setupMessageListeners();
}
void _setupMessageListeners() {
// Listen for message storage
WKIM.shared.messageManager.addOnMsgInsertedListener((wkMsg) {
_handleMessageInserted(wkMsg);
});
// Listen for new messages
WKIM.shared.messageManager.addOnNewMsgListener('global', (msgs) {
_handleNewMessages(msgs);
});
// Listen for message updates
WKIM.shared.messageManager.addOnRefreshMsgListener('global', (wkMsg) {
_handleMessageUpdate(wkMsg);
});
}
void _handleMessageInserted(WKMsg message) {
// Message saved to database, update UI immediately
final channelKey = '${message.channelID}_${message.channelType}';
_channelMessages[channelKey] ??= [];
_channelMessages[channelKey]!.add(message);
// Notify UI
_newMessagesController.add([message]);
}
void _handleNewMessages(List<WKMsg> messages) {
for (var message in messages) {
final channelKey = '${message.channelID}_${message.channelType}';
_channelMessages[channelKey] ??= [];
// Check if message already exists
final existingIndex = _channelMessages[channelKey]!
.indexWhere((m) => m.clientMsgNO == message.clientMsgNO);
if (existingIndex >= 0) {
// Update existing message
_channelMessages[channelKey]![existingIndex] = message;
} else {
// Add new message
_channelMessages[channelKey]!.add(message);
}
}
// Notify UI
_newMessagesController.add(messages);
}
void _handleMessageUpdate(WKMsg message) {
final channelKey = '${message.channelID}_${message.channelType}';
if (_channelMessages.containsKey(channelKey)) {
final messages = _channelMessages[channelKey]!;
final index = messages.indexWhere((m) => m.clientMsgNO == message.clientMsgNO);
if (index >= 0) {
messages[index] = message;
_messageUpdateController.add(message);
}
}
}
// Send text message
Future<void> sendTextMessage(String text, WKChannel channel) async {
try {
final textContent = WKTextContent(text);
await WKIM.shared.messageManager.sendMessage(textContent, channel);
} catch (e) {
print('Failed to send text message: $e');
rethrow;
}
}
// Send image message
Future<void> sendImageMessage(String imagePath, WKChannel channel, {int? width, int? height}) async {
try {
final imageContent = WKImageContent(width ?? 0, height ?? 0);
imageContent.localPath = imagePath;
await WKIM.shared.messageManager.sendMessage(imageContent, channel);
} catch (e) {
print('Failed to send image message: $e');
rethrow;
}
}
// Load history messages
Future<List<WKMsg>> loadHistoryMessages(
String channelID,
int channelType, {
int oldestOrderSeq = 0,
bool contain = false,
int pullMode = 0,
int limit = 20,
int aroundMsgOrderSeq = 0,
}) async {
final completer = Completer<List<WKMsg>>();
WKIM.shared.messageManager.getOrSyncHistoryMessages(
channelID,
channelType,
oldestOrderSeq,
contain,
pullMode,
limit,
aroundMsgOrderSeq,
(messages) {
// Update local cache
final channelKey = '${channelID}_$channelType';
_channelMessages[channelKey] = messages;
completer.complete(messages);
},
() {
// Sync callback - show loading
print('Syncing messages for channel $channelID...');
},
);
return completer.future;
}
// Get messages for a channel
List<WKMsg> getMessagesForChannel(String channelID, int channelType) {
final channelKey = '${channelID}_$channelType';
return _channelMessages[channelKey] ?? [];
}
// Clear messages for a channel
void clearMessagesForChannel(String channelID, int channelType) {
final channelKey = '${channelID}_$channelType';
_channelMessages.remove(channelKey);
}
void dispose() {
_newMessagesController.close();
_messageUpdateController.close();
// Remove listeners
WKIM.shared.messageManager.removeNewMsgListener('global');
WKIM.shared.messageManager.removeOnRefreshMsgListener('global');
}
}
Offline Messages
Need to implement sync channel message data source Channel Message Data Source
Because WuKongIM supports permanent message storage, it will generate massive offline messages. For this, we adopt an on-demand pull mechanism. For example, with 10 conversations each having 100,000 messages, WuKongIM will not pull all 10*100,000=1 million messages to local storage. Instead, it pulls information for these 10 conversations and the corresponding latest 20 messages, which means actually only 200 messages are pulled. Compared to 1 million messages, this greatly improves offline pull speed. Users will only pull messages for a specific conversation when they enter that conversation. These mechanisms are already encapsulated within the SDK, so users don’t need to worry about them. Users only need to focus on recent conversation changes and listen for data retrieval callbacks.
Data Structure Description
Message Class Core Properties
class WKMsg {
// Message header redDot: whether to show red dot noPersist: whether not to store syncOnce: whether to sync only once
MessageHeader header = MessageHeader();
// Message settings receipt: whether receipt, topic: whether topic chat, stream: whether stream message;
Setting setting = Setting();
// Server message ID (globally unique, unordered)
String messageID = "";
// Server message ID (ordered)
int messageSeq = 0;
// Local message ordered ID
int clientSeq = 0;
// 10-digit timestamp
int timestamp = 0;
// Local unique ID
String clientMsgNO = "";
// Sender
String fromUID = "";
// Channel ID
String channelID = "";
// Channel type
int channelType = WKChannelType.personal;
// Message content type e.g. 1:[Text] 2:[Image]...
int contentType = 0;
// Message payload
String content = "";
// Message status 0.sending 1.success
int status = 0;
// Whether deleted 1.yes
int isDeleted = 0;
// Sender's profile
WKChannel? _from;
// Channel profile
WKChannel? _channelInfo;
// Sender's type profile in channel (only for group messages)
WKChannelMember? _memberOfFrom;
// Sort number
int orderSeq = 0;
// Local extension fields
dynamic localExtraMap;
// Remote extension fields, maintained by server
WKMsgExtra? wkMsgExtra;
// Message reaction data
List<WKMsgReaction>? reactionList;
// Message content body contentType==1.WKTextContent contentType==2.WKImageContent
WKMessageContent? messageContent;
}
Message Content Body
class WKMessageContent {
// Message type 1.text 2.image
var contentType = 0;
// Message content
String content = "";
// Reply message
WKReply? reply;
// Message content rendering data
List<WKMsgEntity>? entities;
// Mention information
WKMentionInfo? mentionInfo;
}
Next Steps