Skip to main content

Sending Messages

Description

Method for sending messages
 /**
  *  Send message
  * @param model  Message content
  * @param channel Channel object (personal channel, group channel)
*/
WKIM.shared.messageManager().send(model: WKMessageContent, channel: WKChannel);

Text Messages

// Text message
let textModel: WKTextContent = new WKTextContent('Hello, WuKong')

// Send to user A
WKIM.shared.messageManager().send(textModel, new WKChannel('A', WKChannelType.personal));

Image Messages

// Image message
let imageModel: WKImageContent = new WKImageContent(localPath)
imageModel.width = 100
imageModel.height = 100

// Send to user A
WKIM.shared.messageManager().send(imageModel, new WKChannel('A', WKChannelType.personal));

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
WKIM.shared.messageManager().addInsertedListener((msg) => {
    // Display message in UI
})

New Messages

Listen for new message events
// New message listener
newMsgsListener = (msgs: WKMsg[]) => {
   // Handle new messages
  }

// Listen for new messages
WKIM.shared.messageManager().addNewMsgListener(this.newMsgsListener)

// Remove new message listener
WKIM.shared.messageManager().removeNewMsgListener(this.newMsgsListener)

Message Refresh

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.
// Refresh message listener
refreshMsgListener = (msg: WKMsg) => {
// Handle refresh message
}

// Listen for refresh messages
WKIM.shared.messageManager().addRefreshListener(this.refreshMsgListener)

// Remove refresh message listener
WKIM.shared.messageManager().removeRefreshListener(this.refreshMsgListener)

View Chat Information for a Channel

let option = new ChannelMsgOptions(() => {
      // Syncing - show loading as needed
    }, (list) => {
        // Message data
    })
option.oldestOrderSeq = 0 // Last message's large orderSeq, pass 0 for first entry into chat
option.contain = false // Whether to include the oldestOrderSeq message
option.pullMode = 1 // Pull mode 0: pull down 1: pull up
option.limit = 20 // Number to get each time
option.aroundMsgOrderSeq = 0 // Query messages around this message, e.g. aroundMsgOrderSeq=20 returns [16,17,19,20,21,22,23,24,25]
// View chat information for a channel
WKIM.shared.messageManager().getOrSyncHistoryMessages(channel, option)
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

import { WKIM, WKMsg, WKMessageContent, WKTextContent, WKImageContent, WKChannel, WKChannelType, ChannelMsgOptions } from '@wukong/wkim';

@Component
export struct MessageManager {
  @State private messages: WKMsg[] = [];
  @State private isLoading: boolean = false;
  
  private newMsgsListener = (msgs: WKMsg[]) => {
    this.handleNewMessages(msgs);
  }
  
  private refreshMsgListener = (msg: WKMsg) => {
    this.handleMessageRefresh(msg);
  }
  
  private insertedListener = (msg: WKMsg) => {
    this.handleMessageInserted(msg);
  }
  
  aboutToAppear(): void {
    this.setupMessageListeners();
  }
  
  aboutToDisappear(): void {
    this.removeMessageListeners();
  }
  
  private setupMessageListeners(): void {
    // Listen for message storage
    WKIM.shared.messageManager().addInsertedListener(this.insertedListener);
    
    // Listen for new messages
    WKIM.shared.messageManager().addNewMsgListener(this.newMsgsListener);
    
    // Listen for message refresh
    WKIM.shared.messageManager().addRefreshListener(this.refreshMsgListener);
  }
  
  private removeMessageListeners(): void {
    WKIM.shared.messageManager().removeInsertedListener(this.insertedListener);
    WKIM.shared.messageManager().removeNewMsgListener(this.newMsgsListener);
    WKIM.shared.messageManager().removeRefreshListener(this.refreshMsgListener);
  }
  
  private handleMessageInserted(msg: WKMsg): void {
    // Message saved to database, update UI immediately
    console.log('Message inserted:', msg.clientMsgNo);
    this.addMessageToUI(msg);
  }
  
  private handleNewMessages(msgs: WKMsg[]): void {
    console.log('Received new messages:', msgs.length);
    msgs.forEach(msg => {
      this.addMessageToUI(msg);
    });
  }
  
  private handleMessageRefresh(msg: WKMsg): void {
    console.log('Message refreshed:', msg.clientMsgNo);
    this.updateMessageInUI(msg);
  }
  
  private addMessageToUI(msg: WKMsg): void {
    // Check if message already exists
    const existingIndex = this.messages.findIndex(m => m.clientMsgNo === msg.clientMsgNo);
    
    if (existingIndex >= 0) {
      // Update existing message
      this.messages[existingIndex] = msg;
    } else {
      // Add new message
      this.messages.push(msg);
    }
    
    // Sort messages by timestamp
    this.messages.sort((a, b) => a.timestamp - b.timestamp);
  }
  
  private updateMessageInUI(msg: WKMsg): void {
    const index = this.messages.findIndex(m => m.clientMsgNo === msg.clientMsgNo);
    if (index >= 0) {
      this.messages[index] = msg;
    }
  }
  
  // Send text message
  public sendTextMessage(text: string, channel: WKChannel): void {
    try {
      const textModel = new WKTextContent(text);
      WKIM.shared.messageManager().send(textModel, channel);
    } catch (error) {
      console.error('Failed to send text message:', error);
    }
  }
  
  // Send image message
  public sendImageMessage(localPath: string, width: number, height: number, channel: WKChannel): void {
    try {
      const imageModel = new WKImageContent(localPath);
      imageModel.width = width;
      imageModel.height = height;
      WKIM.shared.messageManager().send(imageModel, channel);
    } catch (error) {
      console.error('Failed to send image message:', error);
    }
  }
  
  // Load history messages
  public loadHistoryMessages(channel: WKChannel, oldestOrderSeq: number = 0): void {
    this.isLoading = true;
    
    const option = new ChannelMsgOptions(
      () => {
        // Syncing callback
        console.log('Syncing messages...');
      },
      (list: WKMsg[]) => {
        // Message data callback
        this.isLoading = false;
        this.handleHistoryMessages(list);
      }
    );
    
    option.oldestOrderSeq = oldestOrderSeq;
    option.contain = false;
    option.pullMode = 0; // Pull down
    option.limit = 20;
    option.aroundMsgOrderSeq = 0;
    
    WKIM.shared.messageManager().getOrSyncHistoryMessages(channel, option);
  }
  
  private handleHistoryMessages(messages: WKMsg[]): void {
    console.log('Loaded history messages:', messages.length);
    
    // Merge with existing messages
    messages.forEach(msg => {
      const existingIndex = this.messages.findIndex(m => m.clientMsgNo === msg.clientMsgNo);
      if (existingIndex < 0) {
        this.messages.push(msg);
      }
    });
    
    // Sort messages by timestamp
    this.messages.sort((a, b) => a.timestamp - b.timestamp);
  }
  
  // Get messages for specific channel
  public getMessagesForChannel(channelId: string, channelType: number): WKMsg[] {
    return this.messages.filter(msg => 
      msg.channelId === channelId && msg.channelType === channelType
    );
  }
  
  // Clear messages
  public clearMessages(): void {
    this.messages = [];
  }
  
  build() {
    Column() {
      Text('Message Manager')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 20 })
      
      if (this.isLoading) {
        Row() {
          LoadingProgress()
            .width(20)
            .height(20)
            .margin({ right: 10 })
          Text('Loading messages...')
        }
        .margin({ bottom: 20 })
      }
      
      Text(`Total Messages: ${this.messages.length}`)
        .fontSize(16)
        .margin({ bottom: 20 })
      
      List() {
        ForEach(this.messages, (message: WKMsg) => {
          ListItem() {
            this.buildMessageItem(message)
          }
        })
      }
      .layoutWeight(1)
      
      Row() {
        TextInput({ placeholder: 'Enter message...' })
          .layoutWeight(1)
          .onSubmit((value: string) => {
            const channel = new WKChannel('test_user', WKChannelType.personal);
            this.sendTextMessage(value, channel);
          })
        
        Button('Send')
          .onClick(() => {
            // Send message logic
          })
      }
      .padding(10)
    }
    .width('100%')
    .height('100%')
    .padding(20)
  }
  
  @Builder
  buildMessageItem(message: WKMsg) {
    Column() {
      Row() {
        Text(message.fromUID)
          .fontSize(14)
          .fontColor(Color.Gray)
          .margin({ right: 10 })
        
        Text(this.formatTimestamp(message.timestamp))
          .fontSize(12)
          .fontColor(Color.Gray)
      }
      .width('100%')
      .justifyContent(FlexAlign.SpaceBetween)
      .margin({ bottom: 5 })
      
      Text(message.content)
        .fontSize(16)
        .textAlign(TextAlign.Start)
        .width('100%')
      
      if (message.status === 0) {
        Text('Sending...')
          .fontSize(12)
          .fontColor(Color.Orange)
          .margin({ top: 5 })
      }
    }
    .alignItems(HorizontalAlign.Start)
    .padding(10)
    .margin({ bottom: 10 })
    .backgroundColor(Color.White)
    .borderRadius(8)
  }
  
  private formatTimestamp(timestamp: number): string {
    const date = new Date(timestamp * 1000);
    return date.toLocaleTimeString();
  }
}

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 Structure

export class WKMsg {
  // Server unique message ID
  messageId = "";
  // Server message sequence number
  messageSeq = 0;
  // Client message sequence number
  clientSeq = 0;
  // Message timestamp
  timestamp = 0;
  // Expiration time
  expireTime = 0;
  // Expiration timestamp
  expireTimestamp = 0;
  // Client unique number
  clientMsgNo = "";
  // Sender uid
  fromUID = "";
  // Channel ID
  channelId = "";
  // Channel type
  channelType = WKChannelType.personal;
  // Message type
  contentType = 0;
  // Message content string
  content = "";
  // Message status 1.sent successfully 0.sending
  status = 0;
  voiceStatus = 0;
  // Is deleted 1.yes
  isDeleted = 0;
  // Search keyword
  searchableWord = "";
  // Message sender profile
  private from?: WKChannel
  // Message channel profile
  private channelInfo?: WKChannel
  // Message sender profile in channel (only for group messages)
  private memberOfFrom?: WKChannelMember
  // Sort number
  orderSeq = 0;
  // Is read
  viewed = 0;
  // Read time
  viewedAt = 0;
  // Topic ID
  topicId = "";
  // Local extensions
  localExtraMap?: Record<string, object>
  // Remote extensions
  wkMsgExtra?: WKMsgExtra
  // Message reaction data
  reactionList?: WKMsgReaction[]
  // Message content
  messageContent?: WKMessageContent
}

Message Content Structure

export class WKMessageContent {
  // Message type
  contentType: number = 0
  // Message content
  content: string = ""
  // Render message content, e.g., need to render @xxx text when @someone
  entities?: ArrayList<WKMsgEntity>
  // Message reply
  reply?: WKReply
  // Mention information
  mentionInfo?: WKMentionInfo
}

Next Steps