> ## Documentation Index
> Fetch the complete documentation index at: https://wukong.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Message Management

> WuKongIM Flutter SDK message management functionality, including message sending/receiving, listening and history messages

## Sending Messages

```dart theme={null}
WKIM.shared.messageManager.sendMessage(WKTextContent('I am a text message'), WKChannel('uid_1', WKChannelType.personal));
```

### Text Messages

```dart theme={null}
// Define text message
WKTextContent text = WKTextContent("Hello, WuKong");
// Send text message
WKIM.shared.messageManager.sendMessage(text, channel);
```

### Image Messages

```dart theme={null}
// 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](/en/sdk/wukongim/flutter/advance#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:

```dart theme={null}
WKIM.shared.messageManager.addOnMsgInsertedListener((wkMsg) {
      // Display in UI
    });
```

## New Messages

Listen for new message events:

```dart theme={null}
// Listen for new message events
WKIM.shared.messageManager.addOnNewMsgListener('chat', (msgs) {
      // Display in UI
    });

// Remove new message listener
WKIM.shared.messageManager.removeNewMsgListener('chat');
```

<Note>
  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`
</Note>

## 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:

```dart theme={null}
// Listen for refresh message events
WKIM.shared.messageManager.addOnRefreshMsgListener('chat', (wkMsg) {
      // TODO refresh message
    });

// Remove refresh message listener
WKIM.shared.messageManager.removeOnRefreshMsgListener('chat');
```

## Message Send Status Code (ReasonCode)

When a message is sent, you can obtain the `WKMsg` object by listening for message refresh events. The `status` (send status) and `reasonCode` in `WKMsg` indicate the result of the message delivery.

| Value | Name                         | Description                                |
| ----- | ---------------------------- | ------------------------------------------ |
| 0     | ReasonUnknown                | Unknown error                              |
| 1     | ReasonSuccess                | Success                                    |
| 2     | ReasonAuthFail               | Authentication failed                      |
| 3     | ReasonSubscriberNotExist     | Subscriber does not exist in the channel   |
| 4     | ReasonInBlacklist            | In blacklist                               |
| 5     | ReasonChannelNotExist        | Channel does not exist                     |
| 6     | ReasonUserNotOnNode          | User is not on node                        |
| 7     | ReasonSenderOffline          | Sender is offline, message delivery failed |
| 8     | ReasonMsgKeyError            | Message key error, invalid message         |
| 9     | ReasonPayloadDecodeError     | Payload decoding failed                    |
| 10    | ReasonForwardSendPacketError | Forwarding send packet failed              |
| 11    | ReasonNotAllowSend           | Not allowed to send message                |
| 12    | ReasonConnectKick            | Connection kicked                          |
| 13    | ReasonNotInWhitelist         | Not in whitelist                           |
| 14    | ReasonQueryTokenError        | Query user token error                     |
| 15    | ReasonSystemError            | System error                               |
| 16    | ReasonChannelIDError         | Wrong channel ID                           |
| 17    | ReasonNodeMatchError         | Node matching error                        |
| 18    | ReasonNodeNotMatch           | Node not matched                           |
| 19    | ReasonBan                    | Channel is banned                          |
| 20    | ReasonNotSupportHeader       | Unsupported header                         |
| 21    | ReasonClientKeyIsEmpty       | clientKey is empty                         |
| 22    | ReasonRateLimit              | Rate limit exceeded                        |
| 23    | ReasonNotSupportChannelType  | Unsupported channel type                   |
| 24    | ReasonDisband                | Channel disbanded                          |
| 25    | ReasonSendBan                | Sending is banned                          |

## View Chat Information for a Channel

```dart theme={null}
/*
    * 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);
```

<Note>
  Getting history messages is not a synchronous method, as there may be non-continuous data that needs to be synced from the server
</Note>

## Complete Message Management Example

```dart theme={null}
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](/en/sdk/wukongim/flutter/datasource#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

```dart theme={null}
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

```dart theme={null}
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

<CardGroup cols={2}>
  <Card title="Channel Management" icon="hash" href="/en/sdk/wukongim/flutter/channel">
    Learn how to manage channels and groups
  </Card>

  <Card title="Conversation Management" icon="users" href="/en/sdk/wukongim/flutter/conversation">
    Handle conversation lists and unread messages
  </Card>

  <Card title="Data Source Configuration" icon="database" href="/en/sdk/wukongim/flutter/datasource">
    Configure message data sources
  </Card>

  <Card title="Advanced Features" icon="cog" href="/en/sdk/wukongim/flutter/advance">
    Explore advanced features and custom messages
  </Card>
</CardGroup>
