# Send and receive messages (/en/realtime-media/im/build/build-core-messaging/messages/send-receive-messages/flutter)

> For AI agents: see the complete documentation index at [llms.txt](/llms.txt).

After logging in to Chat, users can send the following types of messages to a peer user, a chat group, or a chat room:

    * Text messages, including hyperlinks and emojis.

    * Attachment messages, including image, voice, video, and file messages.

    * Location messages.

    * CMD messages.

    * Extended messages.

    * Custom messages.

    The Chat message feature is language agnostic. End users can send messages in any language, as long as their devices support input in that language.

    In addition to sending messages, users can also forward one or more messages. When forwarding multiple messages, users have the following options:

    * Forward messages one-by-one
    * Forward combined messages as message history

    This page shows how to implement sending, receiving, forwarding multiple messages, and modifying sent messages using the Chat SDK.

    ## Understand the tech [#understand-the-tech-3]

    The Chat SDK for Flutter uses the `ChatMessage` and `ChatMessage` classes to send, receive, and withdraw messages.

    The process of sending and receiving a message is as follows:

    1. The message sender creates a text, file, or attachment message using the corresponding `Create` method.
    2. The message sender calls `sendMessage` to send the message.
    3. The message recipient listens for `ChatEventHandler` and receives the message in the `onMessagesReceived` callback.

    ## Prerequisites [#prerequisites-3]

    Before proceeding, ensure that you meet the following requirements:

    * You have integrated the Chat SDK, initialized the SDK, and implemented the functionality of registering accounts and login. For details, see [Chat SDK quickstart](../../../../get-started-sdk).

      Use Chat SDK 1.2 or higher if you intend to enable users to forward multiple messages, or to modify sent messages.

    * You understand the API call frequency limits as described in [Limitations](../../limitations).

    ## Implementation [#implementation-3]

    This section shows how to implement sending and receiving the various types of messages.

    ### Send a message [#send-a-message]

    Follow the steps to create and send a message, and listen for the result of sending the message.

    1. Use the `ChatMessage` class to create a message.

       ```dart
       // Sets the message type. Agora Chat supports 8 message types.
       MessageType messageType = MessageType.TXT;
       // Sets `targetId` to the user ID of the peer user in one-to-one chat, group ID in group chat, and chat room ID in room chat.
       String targetId = "tom";
       // Sets `chatType` as `Chat`, `GroupChat`, or `ChatRoom` for one-to-one chat, group chat, or room chat.
       ChatType chatType = ChatType.Chat;
       // Creates a message. Parameters vary with message types.
       // Creates a text message.
       ChatMessage msg = ChatMessage.createTxtSendMessage(
           targetId: targetId,
           content: "This is text message",
       );
       // Creates an image message. You need to set the local path, length, and width of the image file, and the image name displayed on the UI.
       String imgPath = "/data/.../image.jpg";
       double imgWidth = 100;
       double imgHeight = 100;
       String imgName = "image.jpg";
       int imgSize = 3000;
       ChatMessage msg = ChatMessage.createImageSendMessage(
           targetId: targetId,
           filePath: imgPath,
           width: imgWidth,
           height: imgHeight,
           displayName: imgName,
           fileSize: imgSize,
       );
       // Creates a CMD message. A CMD message contains a command for the recipient to take action. You can customize the command.
       String action = "writing";
       ChatMessage msg = ChatMessage.createCmdSendMessage(
           targetId: targetId,
           action: action,
       );
       // Creates a custom message. A custom message contains the message event type and the extension field.
       // You can customize the extension field according to your use case.
       String event = "gift";
       Map params = {"key": "value"};
       ChatMessage msg = ChatMessage.createCustomSendMessage(
           targetId: targetId,
           event: event,
           params: params,
       );
       // Creates a file message. A file message contains the local file path and the name displayed on the UI.
       String filePath = "data/.../foo.zip";
       String fileName = "foo.zip";
       int fileSize = 6000;
       ChatMessage fileMsg = ChatMessage.createFileSendMessage(
           targetId: targetId,
           filePath: filePath,
           displayName: fileName,
           fileSize: fileSize,
       );
       // Creates a location message. You need to set the longitude and latitude information of the location, as well as the name of the location.
       // To send a location message, you need to integrate a third-party map service to get the longitude and latitude information of the location. When the recipient receives the location information, the service renders the location on the map according to the longitude and latitude information.
       double latitude = 114.78;
       double longitude = 39.89;
       String address = "darwin";
       ChatMessage msg = ChatMessage.createLocationSendMessage(
           targetId: targetId,
           latitude: latitude,
           longitude: longitude,
           address: address,
       );
       // Creates a video message. A video message includes two attachment files, including the video file and the thumbnail of the video. The SDK uses the first frame of the video as the thumbnail. You also need to set the local path, length, width, display name, and duration of the video file.
       String videoPath = "data/.../foo.mp4";
       double videoWidth = 100;
       double videoHeight = 100;
       String videoName = "foo.mp4";
       int videoDuration = 5;
       int videoSize = 4000;
       ChatMessage msg = ChatMessage.createVideoSendMessage(
           targetId: targetId,
           filePath: videoPath,
           width: videoWidth,
           height: videoHeight,
           duration: videoDuration,
           fileSize: videoSize,
           displayName: videoName,
       );
       // Creates a voice message. A voice message contains the local path, display name, and duration of the audio file.
       String voicePath = "data/.../foo.wav";
       String voiceName = "foo.wav";
       int voiceDuration = 5;
       int voiceSize = 1000;
       ChatMessage.createVoiceSendMessage(
           targetId: targetId,
           filePath: voicePath,
           duration: voiceDuration,
           fileSize: voiceSize,
           displayName: voiceName,
       );
       ```

    2. Send the message using `sendMessage` in `ChatManager`.

       ```dart
       ChatClient.getInstance.chatManager.sendMessage(message).then((value) {
       });
       ```

    To get the progress and result of the message sending, you need to implement `ChatManager#addMessageEvent`. For CMD messages which do not necessarily need a result, you do not need to set the callback.

    ```dart
    // Adds a message status listener.
    ChatClient.getInstance.chatManager.addMessageEvent(
        "UNIQUE_HANDLER_ID",
        ChatMessageEvent(
            // Occurs when the message sending succeeds. You can update the message and add other operations in this callback.
            onSuccess: (msgId, msg) {
            // msgId: The pre-sending message ID.
            // msg: The message that is sent successfully.
            },
            // Occurs when the message sending fails. You can update the message status and add other operations in this callback.
            onError: (msgId, msg, error) {
            // msgId: The pre-sending message ID.
            // msg: The message that fails to be sent.
            // error: The error description.
            },
            // For attachment messages such as image, voice, file, and video, you can get a progress value for uploading or downloading them in this callback.
            onProgress: (msgId, progress) {
            // msgId: The pre-sending message ID.
            // progress: The sending progress.
            },
        ),
    );
    void dispose() {
        // Removes the message status listener.
        ChatClient.getInstance.chatManager.removeMessageEvent("UNIQUE_HANDLER_ID");
        super.dispose();
    }
    // The result of the messaging sending is returned in the callback. The return value of the method only indicates the result of the message call.
    ChatClient.getInstance.chatManager.sendMessage(message).then((value) {
       // Finishes sending the message.
    });
    ```

    ### Send a message to online users only [#send-a-message-to-online-users-only-3]

    Agora Chat supports delivering messages only to users that are currently online. An example of using this function is displaying the votes in a group voting: Only users that are online need to see the changes in real time, while the others only need the final result.

    All types of messages in one-to-one chats and chat groups support this function. Compared to an ordinary message, a message that is delivered only to online users has the following differences:

    * Does not support offline storage: If the recipient is offline when sending a message, the message cannot be received, even after logging in again. For ordinary messages, when the recipient is online, the message reminder is received in real time; when the recipient is offline, the offline push message is sent in real time. When the recipient is online again, the Chat server actively pushes the offline message to the client.
    * Support local storage: After a message is successfully sent, it is added to the database.
    * Roaming storage is not supported by default: Sent messages are not stored on the Chat message server by default, so users cannot obtain the messages on other devices. If you need to activate roaming storage of online messages, contact [support@agora.io](mailto\:support@agora.io).

    To deliver messages only to online users, set `Message#deliverOnlineOnly` to `true` when sending the message. The following is an example of sending a text message to only online users:

    ```dart
    // Create a text message.
    final msg = ChatMessage.createTxtSendMessage(
      // `targetId`: The message recipient, which is the user ID of the peer user for one-to-one chat and group ID for group chat.
      targetId: conversationId,
      // `content`: The text content of the message.
      content: 'hello',
      // The chat type, which is `Chat` for one-to-one chat and `GroupChat` for group chat. The default value is `Chat`.
      chatType: ChatType.Chat,
    );

    msg.deliverOnlineOnly = true;

    // Send a message.
    ChatClient.getInstance.chatManager.sendMessage(msg);
    ```

    ### Set message priority [#set-message-priority-3]

    In high-concurrency use-cases, you can set a certain message type or messages from a chat room member as high, normal, or low priority.
    In this case, low-priority messages are dropped first to reserve resources for the high-priority ones, for example, gifts and announcements, when the server is overloaded.
    This ensures that the high-priority messages can be dealt with first when loads of messages are being sent in high concurrency or high frequency.
    Note that this feature can increase the delivery reliability of high-priority messages, but cannot guarantee the deliveries.
    Even high-priorities messages can be dropped when the server load goes too high.

    You can set the priority for all types of messages in the chat room.

    ```dart
    // Set the priority of the chat room message. The default priority is `Normal`, indicating the normal priority.
    if (msg.chatType == ChatType.ChatRoom) {
        msg.chatroomMessagePriority = ChatRoomMessagePriority.High;
    }
    ```

    ### Receive the message [#receive-the-message]

    You can use `ChatEventHandler` to listen for message events. You can add multiple `ChatEventHandler` objects to listen for multiple events. When you no longer listen for an event, for example, when you call `dispose`, ensure that you remove the object.

    When a message arrives, the recipient receives an `onMessagesReceived` callback. Each callback contains one or more messages. You can traverse the message list, and parse and render these messages in this callback.

    ```dart
    // Adds a chat event handler.
    ChatClient.getInstance.chatManager.addEventHandler(
        "UNIQUE_HANDLER_ID",
        ChatEventHandler(
            onMessagesReceived: (messages) {},
        ),
    );
    ...
    // Removes the chat event handler.
    ChatClient.getInstance.chatManager.removeEventHandler("UNIQUE_HANDLER_ID");
    ```

    When a voice message is received, the SDK automatically downloads the audio file.

    For image and video messages, the SDK automatically generates a thumbnail when you create the message. When an image or video message is received, the SDK automatically downloads the thumbnail. To manually download the attachment files, follow the steps:

    1. Set `isAutoDownloadThumbnail` as `false` when initializing the SDK.

       ```dart
       ChatOptions options = ChatOptions(
           appKey: "",
           isAutoDownloadThumbnail: false,
       );
       ```

    2. Set the status callback for the download.

       ```dart
       // Adds a message status listener.
       ChatClient.getInstance.chatManager.addMessageEvent(
           "UNIQUE_HANDLER_ID",
           ChatMessageEvent(
               // Occurs when the message is downloaded successfully.
               onSuccess: (msgId, msg) {
               // msgId: The message ID.
               // msg: The message that is downloaded successfully.
               },
               // Occurs when the message fails to be downloaded.
               onError: (msgId, msg, error) {
               // msgId: The message ID.
               // msg: The message that fails to be downloaded.
               // error: The reason for the download failure.
               },
               // For attachment messages such as image, voice, file, and video, you can get a progress value for uploading or downloading them in this callback.
               onProgress: (msgId, progress) {
               // msgId: The message ID.
               // progress: The upload or download progress.
               },
           ),
       );
       void dispose() {
           // Removes the message status listener.
           ChatClient.getInstance.chatManager.removeMessageEvent("UNIQUE_HANDLER_ID");
           super.dispose();
       }
       ```

    3. Download the thumbnail and get the path to it.

       ```dart
       ChatClient.getInstance.chatManager.downloadThumbnail(message);
       ```

    Get the path to the thumbnail from the `thumbnailLocalPath` member in the message body.

    ```dart
    // Get the message body of the image file.
    ChatImageMessageBody imgBody = message.body as ChatImageMessageBody;
    // Get the local path to the thumbnail.
    String? thumbnailLocalPath = imgBody.thumbnailLocalPath;
    ```

    4. Call `downloadAttachment` to download the file.

       ```dart
       ChatClient.getInstance.chatManager.downloadAttachment(message);
       ```

    Get the path to the attachment from the `localPath` member in the message body.

    ```dart
    // Get the message body.
    ChatFileMessageBody fileBody = message.body as ChatFileMessageBody;
    // Get the local path of the file.
    String? localPath = fileBody.localPath;
    ```

    ### Send and receive a GIF image message [#send-and-receive-a-gif-image-message-3]

    Since SDK v1.4.0, you can send and receive GIF image messages. A GIF image message is a subtype of image message that is **not compressed** when sent. Thumbnail generation and download are the same as for a regular image message.

    To send a GIF image message, set `isGif` to `true` when you create the image message with `createImageSendMessage`:

    ```dart
    final msg = ChatMessage.createImageSendMessage(
      targetId: targetId,
      filePath: filePath,
      isGif: true,
    );

    ChatClient.getInstance.chatManager.sendMessage(msg);
    ```

    As with a regular message, the recipient receives the `onMessagesReceived` callback. After confirming that the message is an image message, read the `isGif` property of the message body. If it returns `true`, the message is a GIF image message.

    ```dart
    final handler = ChatEventHandler(
      onMessagesReceived: (messages) {
        for (final message in messages) {
          final body = message.body;
          if (body is ChatImageMessageBody && body.isGif) {
            // Handle the GIF image message according to your business logic, for example, download and display it.
          }
        }
      },
    );

    ChatClient.getInstance.chatManager.addEventHandler(
      'UNIQUE_HANDLER_ID',
      handler,
    );
    ```

    ### Recall a message [#recall-a-message-3]

    After a message is sent, you can recall it. The `recallMessage` method recalls a message that is saved both locally and on the server, whether it is a historical message, offline message or a roaming message on the server, or a message in the memory or local database of the message sender or recipient.

    The default time limit for recalling a message is two minutes. You can extend this time frame to up to 7 days in [Agora Console](https://console.agora.io/v2). To do so, select a project that enables Agora Chat, then click **Configure** > **Features** > **Message recall**.

    The following permission rules apply to message recall:

    * In one-to-one chats, only the message sender can recall a message they sent. If the message has expired, the recall fails.
    * In chat groups and chat rooms, regular members can recall only the messages they sent, and the recall fails if the message has expired. Since SDK v1.4.0, the chat group owner, chat room owner, and admins can recall messages sent by other members, even after those messages have expired.

    ![message-recall](https://assets-docs.agora.io/images/im/message-recall.png)

    <CalloutContainer type="info">
      <CalloutDescription>
        1. Except CMD messages, you can recall all types of message. 2. If an attachment message, like an image, voice, video, or file message, is recalled, the attachment of the message is also deleted.
      </CalloutDescription>
    </CalloutContainer>

    ```dart
    try {
      await ChatClient.getInstance.chatManager.recallMessage(msgId);
    } on ChatError catch (e) {
    ```

    You can also use `ChatEventHandler` to listen for the state of recalling the message:

    ```dart
    ChatClient.getInstance.chatManager.addEventHandler(
        "UNIQUE_HANDLER_ID",
        ChatEventHandler(
        onMessagesRecalled: (messages) {},
        ),
    );
    ```

    ### Use message extensions [#use-message-extensions-3]

    If the message types listed above do not meet your requirements, you can use message extensions to add attributes to the message. This can be applied in more complicated messaging use-cases.

    ```typescript
    try {
      final msg = ChatMessage.createTxtSendMessage(
        targetId: targetId,
        content: 'content',
      );

      msg.attributes = {'k': 'v'};
      ChatClient.getInstance.chatManager.sendMessage(msg);
    } on ChatError catch (e) {}

    ```

    ### Forward a single message [#forward-a-single-message-2]

    You can use the `Message#createSendMessage` method and the `Message#attribute` property to create a new message identical to the original one, including its body and extended fields. After creating the message, you can forward it using the `ChatManager#sendMessage` method.

    You can forward all types of messages in individual chats, chat groups, and chat rooms. For messages with attachments, there's no need to re-upload the attachments during forwarding.

    However, if the original message expires (for example, deleted from the server due to storage limitations), the recipient can view the attachment address after forwarding but won't be able to download the attachment.

    To forward a single message, refer to the following code:

    ```dart
    void forwardMessage(ChatMessage message) async {
      var msg = ChatMessage.createSendMessage(
        to: message.to,
        body: message.body,
        chatType: message.chatType,
      );
      ChatClient.getInstance.chatManager.sendMessage(msg);
    }
    ```

    ### Forward multiple messages [#forward-multiple-messages-3]

    Supported types for forwarded messages include text, images, audio & video files, attachment, and custom messages. A user can create a combined message with a list of original messages and send it. When receiving a combined message, the recipient can select it and other messages to create a new layered combined message. A combined message can contain up to 10 layers of messages, with at most 300 messages at each layer.

    To forward and receive combined messages, refer to the following code:

    1. Create a combined message using multiple message IDs:

       ```dart
       String title = "Historical messages for one-to-one chats between A and B";
       String summary =
           "A: These are historical messages from A. \nB: These are historical messages from B.";
       String compatibleText =
           "Your current version does not support this type of message. Please upgrade to the latest version.";
       final msg = ChatMessage.createCombineSendMessage(
           targetId: targetId,
           msgIds: msgIds,
           title: title,
           summary: summary,
           compatibleText: compatibleText,
       );
       await ChatClient.getInstance.chatManager.sendMessage(msg);
       ```

    2. Download and parse combined messages:

       ```dart
       List list =
           await ChatClient.getInstance.chatManager.fetchCombineMessageDetail(
       message: combineMessage,
       );
       ```

    For further details see [Multiple messages forwarding limitations](../../limitations#multiple-messages-forwarding-limitations).

    ### Modify sent messages [#modify-sent-messages-3]

    For messages that have been sent successfully, the SDK supports modifying the message content.

    * Before SDK v1.4.0, you could only modify text messages sent in one-to-one chats and chat groups.
    * Since SDK v1.4.0, you can modify various message types sent in one-to-one chats, chat groups, and chat rooms:
      * Text and custom messages: modify both the message body and the extension attributes (`attributes`).
      * File, video, voice, image, location, and combined messages: modify the extension attributes (`attributes`) only.
      * Command messages: modification is not supported.

    There is no time limit for modifying a message, that is, it can be modified as long as the message is still stored on the server. After the message is modified, the message life cycle, that is, its storage time on the server, is recalculated. For example, a message can be stored on the server for 180 days, and the user modifies it on the 30th day after the message was sent. Instead of remaining 150 days, the message can be now stored on the server for 180 days after successful modification.

    In the modified message, the message ID remains unchanged. The message body, the extension attributes, or both are edited and the following items are added:

    * The operator ID of the user performing the action.
    * The operation time that indicates when the message was edited.
    * The number of times a message is edited (up to 10 times).

    For the edited message, other information included in the message, such as the message sender and recipient, remains unchanged.

    To modify a sent message, refer to the following code:

    1. Call `modifyMessage` with the message ID, the new message body, and the new extension attributes:

       ```dart
       // Text message: you can modify both the message body and the extension attributes.
       final txtBody = ChatTextMessageBody(content: 'new content');
       await ChatClient.getInstance.chatManager.modifyMessage(
         messageId: messageId,
         msgBody: txtBody,
         attributes: {'newKey': 'new value'},
       );

       // Custom message: you can modify both the message body and the extension attributes.
       final customBody = ChatCustomMessageBody(event: 'new event');
       await ChatClient.getInstance.chatManager.modifyMessage(
         messageId: messageId,
         msgBody: customBody,
         attributes: {'newKey': 'new value'},
       );

       // File, video, voice, image, location, or combined message: you can modify the extension attributes only. Omit the message body.
       await ChatClient.getInstance.chatManager.modifyMessage(
         messageId: messageId,
         attributes: {'newKey': 'new value'},
       );
       ```

    2. Receive notification of messages modified by other users:

       ```dart
       ChatEventHandler(
               onMessageContentChanged: (message, operatorId, operationTime) {},
       )
       ```

    For further details see [Sent message modification limitations](../../limitations#sent-message-modification-limitations).

    ## Next steps [#next-steps-3]

    After implementing sending and receiving messages, you can refer to the following documents to add more messaging functionalities to your app:

    * [Manage local messages](./manage-messages)

    * [Retrieve conversations and messages from the server](./retrieve-messages)

    * [Message receipts](./message-receipts)

    
  
      
  
      
  
      
  
