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

# Messaging

> Learn how to send and receive messages, mark messages as delivered or read, etc.

## Before you begin

1. Register a [QuickBlox account](https://admin.quickblox.com/signin). This is a matter of a few minutes and you will be able to use this account to build your apps.
2. Configure QuickBlox SDK for your app. Check out [Setup](/sdks/android-setup) page for more details.
3. Create a user session to be able to use QuickBlox functionality. See [Authentication](/sdks/android-authentication) page to learn how to do it.
4. Connect to the Chat server. See [Connection](/sdks/android-chat-connection) page to learn how to do it.
5. Create a dialog. See [Dialogs](/sdks/android-chat-dialogs) page to learn how to do it.

Visit [Key Concepts](/docs/key-concepts) page to learn the most important QuickBlox concepts.

## Subscribe message events

To add the listener to receive messages in real-time, use the `addDialogMessageListener()` method. The listener enables the app to listen to the incoming message.

<Tabs>
  <Tab title="Java">
    ```Java theme={null}
    QBChatService chatService = QBChatService.getInstance();
    QBIncomingMessagesManager incomingMessagesManager = chatService.getIncomingMessagesManager();

    incomingMessagesManager.addDialogMessageListener(new QBChatDialogMessageListener() {
        @Override
        public void processMessage(String dialogId, QBChatMessage chatMessage, Integer senderId) {

        }

        @Override
        public void processError(String dialogId, QBChatException exception, QBChatMessage chatMessage, Integer senderId) {

        }
    });
    ```
  </Tab>

  <Tab title="Kotlin">
    ```Kotlin theme={null}
    val chatService = QBChatService.getInstance()
    val incomingMessagesManager = chatService.incomingMessagesManager

    incomingMessagesManager.addDialogMessageListener(object : QBChatDialogMessageListener {
        override fun processMessage(dialogId: String?, chatMessage: QBChatMessage?, senderId: Int?) {

        }

        override fun processError(dialogId: String?, exception: QBChatException?, chatMessage: QBChatMessage?, senderId: Int?) {

        }
    })
    ```
  </Tab>
</Tabs>

You can add a message listener for a particular dialog if needed.

<Tabs>
  <Tab title="Java">
    ```Java theme={null}
    privateDialog.addMessageListener(new QBChatDialogMessageListener() {
        @Override
        public void processMessage(String dialogId, QBChatMessage chatMessage, Integer senderId) {

        }

        @Override
        public void processError(String dialogId, QBChatException exception, QBChatMessage chatMessage, Integer senderId) {

        }
    });
    ```
  </Tab>

  <Tab title="Kotlin">
    ```Kotlin theme={null}
    privateDialog.addMessageListener(object : QBChatDialogMessageListener {
        override fun processMessage(dialogId: String?, qbChatMessage: QBChatMessage?, senderId: Int?) {

        }

        override fun processError(dialogId: String?, e: QBChatException?, qbChatMessage: QBChatMessage?, senderId: Int?) {

        }
    })
    ```
  </Tab>
</Tabs>

## Send text message

To send a message to a **private**, **group** or **public** dialog, use the code snippet below.

<Tabs>
  <Tab title="Java">
    ```Java theme={null}
    QBChatMessage chatMessage = new QBChatMessage();
    chatMessage.setBody("Hello QuickBlox!");
    chatMessage.setSaveToHistory(true);

    // if you want to use this feature without callbacks:
    //try{
    //    privateDialog.sendMessage(chatMessage);
    //} catch (SmackException.NotConnectedException e) {
    //
    //}

    dialog.sendMessage(chatMessage, new QBEntityCallback<Void>() {
        @Override
        public void onSuccess(Void aVoid, Bundle bundle) {

        }

        @Override
        public void onError(QBResponseException exception) {

        }
    });
    ```
  </Tab>

  <Tab title="Kotlin">
    ```Kotlin theme={null}
    val chatMessage = QBChatMessage()
    chatMessage.body = "Hello QuickBlox!"
    chatMessage.setSaveToHistory(true)

    // If you want to use this feature without callbacks:
    //try{
    //    privateDialog.sendMessage(chatMessage);
    //} catch (SmackException.NotConnectedException e) {
    //
    //}

    dialog.sendMessage(chatMessage, object : QBEntityCallback<Void> {
        override fun onSuccess(aVoid: Void?, bundle: Bundle?) {

        }

        override fun onError(exception: QBResponseException?) {

        }
    })
    ```
  </Tab>
</Tabs>

<Note>
  You need to join the **group** and **public** dialog by calling the `join()` method before you start chatting in a dialog. Once the dialog is joined, you can receive/send messages. See [this section](/sdks/android-chat-dialogs#join-dialog) to learn how to join the dialog.
</Note>

There are two ways to send any message to any dialog:

1. Synchronous.

<Tabs>
  <Tab title="Java">
    ```Java theme={null}
    QBChatMessage chatMessage = new QBChatMessage();
    chatMessage.setBody("Hello QuickBlox!");
    chatMessage.setSaveToHistory(true);

    try {
        dialog.sendMessage(chatMessage);
    } catch (SmackException.NotConnectedException exception) {

    }
    ```
  </Tab>

  <Tab title="Kotlin">
    ```Kotlin theme={null}
    val chatMessage = QBChatMessage()
    chatMessage.body = "Hello QuickBlox!"
    chatMessage.setSaveToHistory(true)

    try {
        dialog.sendMessage(chatMessage)
    } catch (exception: SmackException.NotConnectedException) {

    }
    ```
  </Tab>
</Tabs>

2. Asynchronous.

<Tabs>
  <Tab title="Java">
    ```Java theme={null}
    QBChatMessage chatMessage = new QBChatMessage();
    chatMessage.setBody("Hello QuickBlox!");
    chatMessage.setSaveToHistory(true);

    dialog.sendMessage(chatMessage, new QBEntityCallback<Void>() {
        @Override
        public void onSuccess(Void aVoid, Bundle bundle) {

        }

        @Override
        public void onError(QBResponseException exception) {

        }
    });
    ```
  </Tab>

  <Tab title="Kotlin">
    ```Kotlin theme={null}
    val chatMessage = QBChatMessage()
    chatMessage.body = "Hello QuickBlox!"
    chatMessage.setSaveToHistory(true)

    dialog.sendMessage(chatMessage, object : QBEntityCallback<Void> {
        override fun onSuccess(aVoid: Void?, bundle: Bundle?) {

        }

        override fun onError(e: QBResponseException?) {

        }
    })
    ```
  </Tab>
</Tabs>

You can use the most convenient for you, according to your application logic.

<Tip>
  Make sure to set the `saveToHistory` as `true` to save the message on the server. If the `saveToHistory` is set as `false`, the message won't be saved on the server. However, the message will be delivered to the user in either case.
</Tip>

## Send message with attachment

Chat attachments are supported by the [content API](/sdks/android-content). In order to send a chat attachment, you need to upload the file to QuickBlox cloud storage and obtain a link to the file (file UID). Then you need to include this UID into the chat message and send it.

<Tabs>
  <Tab title="Java">
    ```Java theme={null}
    File filePhoto = new File("image.png");
    boolean fileIsPublic = false;
    String [] tags = new String[]{"tag_1", "tag_2"};

    QBContent.uploadFileTask(filePhoto, fileIsPublic, String.valueOf(tags), new QBProgressCallback() {
        @Override
        public void onProgressUpdate(int i) {
            // i - progress in percents
        }
    }).performAsync(new QBEntityCallback<QBFile>() {
        @Override
        public void onSuccess(QBFile file, Bundle bundle) {
            // create a message
            QBChatMessage chatMessage = new QBChatMessage();
            chatMessage.setSaveToHistory(true); // save a message to history

            // attach a photo
            QBAttachment attachment = new QBAttachment("photo");
            attachment.setId(file.getId().toString());
            chatMessage.addAttachment(attachment);

            // send the message
            // ...
        }

        @Override
        public void onError(QBResponseException exception) {
            // error
        }
    });
    ```
  </Tab>

  <Tab title="Kotlin">
    ```Kotlin theme={null}
    val filePhoto = File("image.png")
    val fileIsPublic = false
    val tags = arrayOf("tag_1", "tag_2")

    QBContent.uploadFileTask(filePhoto, fileIsPublic, tags.toString()) {
        // i - progress in percents
    }.performAsync(object : QBEntityCallback<QBFile> {
        override fun onSuccess(file: QBFile?, bundle: Bundle?) {
            // create a message
            val chatMessage = QBChatMessage()
            chatMessage.setSaveToHistory(true) // Save a message to history

            // attach a photo
            val attachment = QBAttachment("photo")
            attachment.id = file.id.toString()
            chatMessage.addAttachment(attachment)

            // send the message
            // ...
        }

        override fun onError(exception: QBResponseException?) {
            // error
        }
    })
    ```
  </Tab>
</Tabs>

The same flow is supported on the message receiver's side. When you receive a message with an attachment, you need to get the file UID, and then download the file from the cloud storage.

<Tabs>
  <Tab title="Java">
    ```Java theme={null}
    // QBChatDialogMessageListener
    @Override
    public void processMessage(String dialogId, QBChatMessage chatMessage, Integer integer) {
        // count of attachments might be more than one
        for (QBAttachment attachment : chatMessage.getAttachments()) {
            String fileId = attachment.getId();

            // download a file
            QBContent.downloadFile(fileId).performAsync(new QBEntityCallback<InputStream>() {
                @Override
                public void onSuccess(InputStream inputStream, Bundle bundle) {
                    // process file
                }

                @Override
                public void onError(QBResponseException exception) {

                }
            });
        }
    }

    ```
  </Tab>

  <Tab title="Kotlin">
    ```Kotlin theme={null}
    // QBChatDialogMessageListener
    override fun processMessage(dialogId: String, chatMessage: QBChatMessage, integer: Int?) {
        // count of attachments might be more than one
        for (attachment in qbChatMessage.attachments) {
            val fileId = attachment.id

            // download a file
            QBContent.downloadFile(fileId).performAsync(object : QBEntityCallback<InputStream> {
                override fun onSuccess(inputStream: InputStream?, bundle: Bundle?) {
                    // process file
                }

                override fun onError(exception: QBResponseException?) {

                }
            })
        }
    }
    ```
  </Tab>
</Tabs>

## Send message with extra data

You have an option to extend the message with additional fields. Specify one or more **key-value** items in the `message`. Using these items, you can implement the ability for a user to send self-location information to another user or notification messages signifying that a user has left a group, etc.

<Tabs>
  <Tab title="Java">
    ```Java theme={null}
    QBChatMessage chatMessage = new QBChatMessage();
    chatMessage.setSaveToHistory(true);
    chatMessage.setBody("How are you today?");
    chatMessage.setProperty("customParam1", "book");
    chatMessage.setProperty("customParam2", "21");

    qbChatDialog.sendMessage(chatMessage, new QBEntityCallback<Void>() {
        @Override
        public void onSuccess(Void void, Bundle bundle) {

        }

        @Override
        public void onError(QBResponseException exception) {

        }
    });
    ```
  </Tab>

  <Tab title="Kotlin">
    ```Kotlin theme={null}
    val chatMessage = QBChatMessage()
    chatMessage.setSaveToHistory(true)
    chatMessage.isMarkable = true
    chatMessage.body = "How are you today?"
    chatMessage.setProperty("customParam1", "book")
    chatMessage.setProperty("customParam2", "21")

    qbChatDialog.sendMessage(chatMessage, object : QBEntityCallback<Void> {
        override fun onSuccess(aVoid: Void?, bundle: Bundle?) {

        }

        override fun onError(exception: QBResponseException?) {

        }
    })
    ```
  </Tab>
</Tabs>

| Argument      | Required | Description                                  |
| ------------- | -------- | -------------------------------------------- |
| qbChatMessage | yes      | Specifies message fields that should be set. |

Set the following fields of the `qbChatMessage`:

| Field         | Required | Description                                                                                                            |
| ------------- | -------- | ---------------------------------------------------------------------------------------------------------------------- |
| saveToHistory | no       | Specifies if the message will be saved on the server. Set the saveToHistory as true to save the message on the server. |
| body          | no       | A message text.                                                                                                        |
| property      | no       | Extra data. Specify any key-value pairs. In each pair, the key and value are both string values.                       |

## Retrieve chat history

Every dialog stores its chat history that you can retrieve using the `getDialogMessages()` method. The request below will return messages for a specific dialog limited to the 100 dialogs on the page.

<Tabs>
  <Tab title="Java">
    ```Java theme={null}
    QBMessageGetBuilder messageGetBuilder = new QBMessageGetBuilder();
    messageGetBuilder.setLimit(100);

    // if you want to retrieve messages using filtering:
    //messageGetBuilder.gte("date_sent", "508087800");
    //messageGetBuilder.lte("date_sent", "1170720000");
    //messageGetBuilder.markAsRead(false);

    QBRestChatService.getDialogMessages(chatDialog, messageGetBuilder).performAsync(new QBEntityCallback<ArrayList<QBChatMessage>>() {
        @Override
        public void onSuccess(ArrayList<QBChatMessage> chatMessages, Bundle bundle) {

        }

        @Override
        public void onError(QBResponseException exception) {

        }
    });
    ```
  </Tab>

  <Tab title="Kotlin">
    ```Kotlin theme={null}
    val messageGetBuilder = QBMessageGetBuilder()
    messageGetBuilder.limit = 100

    // If you want to retrieve messages using filtering:
    //messageGetBuilder.gte("date_sent", "508087800")
    //messageGetBuilder.lte("date_sent", "1170720000")
    //messageGetBuilder.markAsRead(false)

    QBRestChatService.getDialogMessages(chatDialog, messageGetBuilder).performAsync(object : QBEntityCallback<ArrayList<QBChatMessage>> {
        override fun onSuccess(chatMessages: ArrayList<QBChatMessage>?, bundle: Bundle?) {

        }

        override fun onError(exception: QBResponseException?) {

        }
    })
    ```
  </Tab>
</Tabs>

<Note>
  If you want to mark all retrieved chat messages as a read, set the `markAsRead` parameter as `true`. If you decide not to mark chat messages as read, just set `markAsRead` parameter as `false`.
</Note>

If you want to retrieve only messages updated after some specific date time and order the search results, you can apply operators. This is useful if you cache messages somehow and do not want to obtain the whole list of messages on every app start. Thus, you can apply [search](/sdks/android-chat-messaging#search-operators) and [sort](/sdks/android-chat-messaging#sort-operators) operators to list messages on the page so that it is easier to view specific messages. The operators are set in the `QBMessageGetBuilder` class.

If you want to get a paginated list of messages from the server, you can set the following pagination parameters in the `QBMessageGetBuilder` class.

| Pagination parameter | Description                                                                                 |
| -------------------- | ------------------------------------------------------------------------------------------- |
| skip                 | Skip N records in search results. Useful for pagination. Default (if not specified): **0**. |
| limit                | Limit search results to N records. Useful for pagination. Default value: **100**.           |

### Search operators

You can use search operators to search for the exact data that you need.

| Search operators        | Applicable to types  | Applicable to fields                                 | Description                                          |
| ----------------------- | -------------------- | ---------------------------------------------------- | ---------------------------------------------------- |
| lt(field, searchValue)  | number, string, date | date\_sent, sender\_id, recipient\_id, updated\_at   | **Less Than** operator.                              |
| lte(field, searchValue) | number, string, date | date\_sent, sender\_id, recipient\_id, updated\_at   | **Less Than** or **Equal** to operator.              |
| gt(field, searchValue)  | number, string, date | date\_sent, sender\_id, recipient\_id, updated\_at   | **Greater Than** operator.                           |
| gte(field, searchValue) | number, string, date | date\_sent, sender\_id, recipient\_id, updated\_at   | **Greater Than** or **Equal** to operator.           |
| ne(field, searchValue)  | number, string, date | \_id, message, date\_sent, sender\_id, recipient\_id | **Not Equal** to operator.                           |
| in(field, searchValue)  | number, string, date | date\_sent, sender\_id, recipient\_id                | **IN** array operator.                               |
| nin(field, searchValue) | number, string, date | date\_sent, sender\_id, recipient\_id                | Not **IN** array operator.                           |
| or(field, searchValue)  | number, string, date | date\_sent, sender\_id, recipient\_id                | All records that contain a value 1 **or** value 2.   |
| ctn(field, searchValue) | string               | message                                              | All records that **contain** a particular substring. |

### Sort operators

You can use sort operators to order the search results.

| Sort operator   | Applicable to types | Description                                                               |
| --------------- | ------------------- | ------------------------------------------------------------------------- |
| sortAsc(field)  | All types           | Search results will be sorted in ascending order by the specified field.  |
| sortDesc(field) | All types           | Search results will be sorted in descending order by the specified field. |

## Update message

To update a message text , use the `updateMessage()` method below.

<Tabs>
  <Tab title="Java">
    ```Java theme={null}
    QBMessageUpdateBuilder messageUpdateBuilder = new QBMessageUpdateBuilder();
    messageUpdateBuilder.updateText("Updated message body string");

    // if you want to mark message as Delivered or Read on the server
    //messageUpdateBuilder.markDelivered();
    //messageUpdateBuilder.markRead();

    QBRestChatService.updateMessage(message.getId(), message.getDialogId(), messageUpdateBuilder).performAsync(new QBEntityCallback<Void>() {
        @Override
        public void onSuccess(Void aVoid, Bundle bundle) {

        }

        @Override
        public void onError(QBResponseException exception) {

        }
    });
    ```
  </Tab>

  <Tab title="Kotlin">
    ```Kotlin theme={null}
    val messageUpdateBuilder = QBMessageUpdateBuilder()
    messageUpdateBuilder.updateText("Updated message body string")

    // If you want to mark message as Delivered or Read on the server
    //messageUpdateBuilder.markDelivered();
    //messageUpdateBuilder.markRead();

    QBRestChatService.updateMessage(message.id, message.dialogId, messageUpdateBuilder).performAsync(object : QBEntityCallback<Void> {
        override fun onSuccess(aVoid: Void?, bundle: Bundle?) {

        }

        override fun onError(exception: QBResponseException?) {

        }
    })
    ```
  </Tab>
</Tabs>

<Tip>
  You can also use the `updateMessage()` method to mark message as delivered or read on the server. As a result, the ID of the user who has received the message or read it will be added to the array of `delivered_ids`/`read_ids` field, in the message model. However, the server won't notify the sender with delivery or read receipt that informs about the change in the message model.
</Tip>

## Delete message

Any user in the `occupantIDs` can delete a message from the dialog. As a result, the message will be deleted from the current user history, without affecting the histories of other users.

The owner of the dialog can completely remove messages from all users' histories. This is achieved by setting the `forceDelete` parameter to `true`.

<Tabs>
  <Tab title="Java">
    ```Java theme={null}
    Set<String> messagesIds = new HashSet<>();
    messagesIds.add("456abcdefg9876lmnop23qrst");
    messagesIds.add("456gfedcba5432ponml09xyz0");

    boolean forceDelete = false;

    QBRestChatService.deleteMessages(messagesIds, forceDelete).performAsync(new QBEntityCallback<Void>() {
        @Override
        public void onSuccess(Void aVoid, Bundle bundle) {

        }

        @Override
        public void onError(QBResponseException exception) {

        }
    });
    ```
  </Tab>

  <Tab title="Kotlin">
    ```Kotlin theme={null}
    val messagesIds = HashSet<String>()
    messagesIds.add("456abcdefg9876lmnop23qrst")
    messagesIds.add("456gfedcba5432ponml09xyz0")

    val forceDelete = false

    QBRestChatService.deleteMessages(messagesIds, forceDelete).performAsync(object : QBEntityCallback<Void> {
        override fun onSuccess(aVoid: Void?, bundle: Bundle?) {

        }

        override fun onError(exception: QBResponseException?) {

        }
    })
    ```
  </Tab>
</Tabs>

| Argument    | Required | Description                                                                                            |
| ----------- | -------- | ------------------------------------------------------------------------------------------------------ |
| messagesIDs | yes      | A set of strings with messages IDs.                                                                    |
| forceDelete | yes      | A boolean parameter. Delete message for everyone. Set it as true to perform. Only the owner can do it. |

## Check if a message is sent

The message is considered as **sent** if it has been delivered to the server. To get to know that a message has been delivered to the server, make sure to enable a stream management before connecting to the Chat server. See [this section](/sdks/android-setup#stream-management) to learn how to enable the stream management.

Thus, to track the event when your message becomes sent, use the `QBChatDialogMessageSentListener`.

<Tabs>
  <Tab title="Java">
    ```Java theme={null}
    QBChatDialogMessageSentListener messageSentListener = new QBChatDialogMessageSentListener() {
        @Override
        public void processMessageSent(String dialogId, QBChatMessage chatMessage) {

        }

        @Override
        public void processMessageFailed(String dialogId, QBChatMessage chatMessage) {

        }
    };

    chatDialog.addMessageSentListener(messageSentListener);
    ```
  </Tab>

  <Tab title="Kotlin">
    ```Kotlin theme={null}
    val messageSentListener = object : QBChatDialogMessageSentListener {
        override fun processMessageSent(dialogId: String?, chatMessage: QBChatMessage?) {

        }

        override fun processMessageFailed(dialogId: String?, chatMessage: QBChatMessage?) {

        }
    }

    chatDialog.addMessageSentListener(messageSentListener)
    ```
  </Tab>
</Tabs>

<Warning>
  You should enable Stream Management before you do the `chatService.login()` because the Stream Management is initialized while Chat login is performed.

  The Stream Management defines an extension for active management of a stream between a client and server, including features for stanza acknowledgments.
</Warning>

## Mark message as delivered

As a sender, you may want to be informed that a message has been successfully delivered to the recipient. The mark-as-delivered functionality allows to notify the sender about message delivery.

To track the event when the message has been delivered to the user, use the `QBMessageStatusListener()`.

<Tabs>
  <Tab title="Java">
    ```Java theme={null}
    QBMessageStatusesManager messageStatusesManager = QBChatService.getInstance().getMessageStatusesManager();
    messageStatusesManager.addMessageStatusListener(new QBMessageStatusListener() {
        @Override
        public void processMessageDelivered(String messageId, String dialogId, Integer userId) {

        }
    });
    ```
  </Tab>

  <Tab title="Kotlin">
    ```Kotlin theme={null}
    val messageStatusesManager = QBChatService.getInstance().messageStatusesManager
    messageStatusesManager.addMessageStatusListener(object : QBMessageStatusListener {
        override fun processMessageDelivered(messageId: String?, dialogId: String?, userId: Int?) {

        }
    })
    ```
  </Tab>
</Tabs>

Use the `deliverMessage()` method to mark a message as delivered. As a result, the server will notify a sender about the delivery receipt.

<Tabs>
  <Tab title="Java">
    ```Java theme={null}
    // to manually notify the server that the message has been delivered to you
    try {
        dialog.deliverMessage(message);
    } catch (XMPPException | SmackException.NotConnectedException e) {

    }
    ```
  </Tab>

  <Tab title="Kotlin">
    ```Kotlin theme={null}
    // to manually notify the server that the message has delivered to you
    try {
        dialog.deliverMessage(message)
    } catch (e: XMPPException) {

    } catch (e: SmackException.NotConnectedException) {

    }
    ```
  </Tab>
</Tabs>

A message can be marked as delivered automatically by the server once a message is successfully delivered to the recipient. Set the `markable` as `true` using the `sendMessage()` method if you want, as a sender, to receive message delivery receipts from other recipients. Thus, the `markable` parameter enables the sender to request the delivery receipt. It also enables the recipient to confirm the message delivery. However, if `markable` is `false` or omitted, then you can notify a sender about the delivery receipt using the `deliverMessage()` method.

<Tabs>
  <Tab title="Java">
    ```Java theme={null}
    QBChatMessage chatMessage = new QBChatMessage();
    chatMessage.setBody("This is your message body");
    chatMessage.setMarkable(true); // to be markable as delivered
    chatMessage.setSaveToHistory(true); // to be saved in chat history

    qbChatDialog.sendMessage(chatMessage, new QBEntityCallback<Void>() {
        @Override
        public void onSuccess(Void aVoid, Bundle bundle) {
            // message successfully sent
        }

        @Override
        public void onError(QBResponseException exception) {
            // message Sending Error
        }
    });
    ```
  </Tab>

  <Tab title="Kotlin">
    ```Kotlin theme={null}
    val qbChatMessage = QBChatMessage()
    qbChatMessage.body = "This is your message body"
    qbChatMessage.isMarkable = true //To be markable as delivered
    qbChatMessage.setSaveToHistory(true) // To be saved in chat history

    qbChatDialog.sendMessage(qbChatMessage, object : QBEntityCallback<Void> {
        override fun onSuccess(aVoid: Void?, p1: Bundle?) {
            // message successfully sent
        }

        override fun onError(exception: QBResponseException?) {
            // message Sending Error
        }
    })
    ```
  </Tab>
</Tabs>

<Warning>
  Make sure to understand, that marking-as-delivered operation just confirms the fact of message delivery. The message acquires the **delivered** status when the message delivered event is received.

  When a message is marked as delivered, the IDs of users who have received the message are stored in the message model, on the server. Thus, you can request a chat history from the server to get to know who received the message using the `getDialogMessages()` method. See [this section](/sdks/android-chat-messaging#retrieve-chat-history) to learn how to retrieve chat history.
</Warning>

## Mark message as read

As a sender, you may want to be informed that a message has been read by the recipient. The mark-as-read functionality allows to notify the sender that a message has been read.

To track the event when the message has been read by the user, use the `QBMessageStatusListener()`.

<Tabs>
  <Tab title="Java">
    ```Java theme={null}
    QBMessageStatusesManager messageStatusesManager = QBChatService.getInstance().getMessageStatusesManager();
    messageStatusesManager.addMessageStatusListener(new QBMessageStatusListener() {
        @Override
        public void processMessageRead(String messageId, String dialogId, Integer userId) {

        }
    });
    ```
  </Tab>

  <Tab title="Kotlin">
    ```Kotlin theme={null}
    val messageStatusesManager = QBChatService.getInstance().messageStatusesManager
    messageStatusesManager.addMessageStatusListener(object : QBMessageStatusListener {
        override fun processMessageRead(messageId: String?, dialogId: String?, userId: Int?) {

        }
    })
    ```
  </Tab>
</Tabs>

Use the `readMessage()` method to mark a message as read. As a result, the server will notify a sender about the read receipt.

<Tabs>
  <Tab title="Java">
    ```Java theme={null}
    // to notify the server about displaying message to the user
    try {
        dialog.readMessage(message);
    } catch (XMPPException | SmackException.NotConnectedException exception) {

    }
    ```
  </Tab>

  <Tab title="Kotlin">
    ```Kotlin theme={null}
    // to notify the server about displaying message to the user
    try {
        dialog.readMessage(message)
    } catch (exception: XMPPException) {

    } catch (exception: SmackException.NotConnectedException) {

    }
    ```
  </Tab>
</Tabs>

<Warning>
  When a message is marked as read, the IDs of users who have read the message are stored in the message model, on the server. Thus, you can request a chat history from the server to get to know who read the message using the `getDialogMessages()` method. See [this section](/sdks/android-chat-messaging#retrieve-chat-history) to learn how to retrieve chat history.
</Warning>

## Send typing indicators

You may want, as a sender, to let the recipient know that you are typing the message or have stopped typing the message. Use typing indicators as a form of chat-specific presence. Typing indicators allow to indicate if users are typing messages in a dialog at the moment.

There are the following **typing** notifications supported.

* **typing**. The user is composing a message. The user is actively interacting with a message input interface specific to this chat session (for example, by typing in the input area of a chat window).
* **stopped**. The user had been composing but now has stopped. The user has been composing but has not interacted with the message input interface for a short period of time (for example, 30 seconds).

To track the event when the sender is typing the message, use the `QBChatDialogTypingListener()`.

<Tabs>
  <Tab title="Java">
    ```Java theme={null}
    QBChatDialogTypingListener typingListener = new QBChatDialogTypingListener() {
        @Override
        public void processUserIsTyping(String dialogId, Integer userId) {

        }
    };

    dialog.addIsTypingListener(typingListener);
    ```
  </Tab>

  <Tab title="Kotlin">
    ```Kotlin theme={null}
    val typingListener = object : QBChatDialogTypingListener {
        override fun processUserIsTyping(dialogId: String?, userId: Int?) {

        }
    }

    dialog.addIsTypingListener(typingListener)
    ```
  </Tab>
</Tabs>

To track the event when the sender has stopped typing the message, use the `QBChatDialogTypingListener()`.

<Tabs>
  <Tab title="Java">
    ```Java theme={null}
    QBChatDialogTypingListener typingListener = new QBChatDialogTypingListener() {
        @Override
        public void processUserStopTyping(String dialogId, Integer userId) {

        }
    };

    dialog.addIsTypingListener(typingListener);
    ```
  </Tab>

  <Tab title="Kotlin">
    ```Kotlin theme={null}
    val typingListener = object : QBChatDialogTypingListener {
        override fun processUserStopTyping(dialogId: String?, userId: Int?) {

        }
    }

    dialog.addIsTypingListener(typingListener)
    ```
  </Tab>
</Tabs>

To notify a recipient that a sender is typing the message, use the `sendIsTyping()` method.

<Tabs>
  <Tab title="Java">
    ```Java theme={null}
    // when user starts typing in the message input field
    try {
        dialog.sendIsTypingNotification();
    } catch (XMPPException | SmackException.NotConnectedException exception) {

    }
    ```
  </Tab>

  <Tab title="Kotlin">
    ```Kotlin theme={null}
    // When user starts typing in the message input field
    try {
        dialog.sendIsTypingNotification()
    } catch (exception: XMPPException) {

    } catch (exception: SmackException.NotConnectedException) {
    ```
  </Tab>
</Tabs>

To notify a recipient that a sender had been composing a message but now has stopped, use the `sendStopTypingNotification()` method.

<Tabs>
  <Tab title="Java">
    ```Java theme={null}
    // when the user stops typing and not interact with message input field
    try {
        dialog.sendStopTypingNotification();
    } catch (XMPPException | SmackException.NotConnectedException exception) {

    }
    ```
  </Tab>

  <Tab title="Kotlin">
    ```Kotlin theme={null}
    // When the user stops typing and not interact with message input field
    try {
        dialog.sendStopTypingNotification()
    } catch (exception: XMPPException) {

    } catch (exception: SmackException.NotConnectedException) {
    ```
  </Tab>
</Tabs>

## Send system messages

There is a way to send system messages to other users about some events. For example, a system message can be sent when a user has joined or left a group dialog. These messages are handled over a separate channel and are not be mixed up with regular chat messages. Thus, they are handled by the `QBSystemMessageListener` listener. See how to add the listener in the snippet below.

System messages are also not shown in the dialog history and, consequently, are not stored on the server. This means that these messages will be delivered **only** to online users. Send system messages using the `sendSystemMessage()` method.

<Tabs>
  <Tab title="Java">
    ```Java theme={null}
    private QBSystemMessagesManager systemMessagesManager;
    private QBSystemMessageListener systemMessageListener;

    void setSystemMessagesManager() {
        systemMessagesListener = new QBSystemMessageListener(){
            @Override
            public void processMessage(QBChatMessage chatMessage) {
                // new System Message Received
            }

            @Override
            public void processError(QBChatException exception, QBChatMessage chatMessage) {
                // new System Message Received with Exception
            }
        };
        systemMessagesManager = QBChatService.getInstance().getSystemMessagesManager();
        systemMessagesManager.addSystemMessageListener(systemMessagesListener);
    }

    void sendSystemMessage(QBChatDialog chatDialog, Integer opponentId) {
        QBChatMessage chatMessage = new QBChatMessage();

        chatMessage.setDialogId(chatDialog.getDialogId());
        chatMessage.setRecipientId(opponentId);

        chatMessage.setProperty("custom_property_1", "custom_value_1");
        chatMessage.setProperty("custom_property_2", "custom_value_2");
        chatMessage.setProperty("custom_property_3", "custom_value_3");

        try {
            systemMessagesManager.sendSystemMessage(chatMessage);
        } catch (SmackException.NotConnectedException exception) {
            exception.printStackTrace();
        }
    }
    ```
  </Tab>

  <Tab title="Kotlin">
    ```Kotlin theme={null}
    private lateinit var systemMessagesListener: QBSystemMessageListener
    private lateinit var systemMessagesManager: QBSystemMessagesManager

    fun setSystemMessagesManager() {
        systemMessagesListener = object : QBSystemMessageListener {
            override fun processMessage(chatMessage: QBChatMessage) {
                // new System Message Received
        }

            override fun processError(exception: QBChatException, chatMessage: QBChatMessage) {
                // new System Message Received with Exception
            }
        }
        systemMessagesManager = QBChatService.getInstance().systemMessagesManager
        systemMessagesManager.addSystemMessageListener(systemMessagesListener)
    }

    fun sendSystemMessage(qbChatDialog: chatDialog, opponentId: Int?) {
        val chatMessage = QBChatMessage()

        chatMessage.dialogId = chatDialog.dialogId
        chatMessage.recipientId = opponentId

        chatMessage.setProperty("custom_property_1", "custom_value_1")
        chatMessage.setProperty("custom_property_2", "custom_value_2")
        chatMessage.setProperty("custom_property_3", "custom_value_3")

        try {
            systemMessagesManager.sendSystemMessage(chatMessage)
        } catch (exception: SmackException.NotConnectedException) {
            exception.printStackTrace()
        }
    }
    ```
  </Tab>
</Tabs>

| Argument      | Description                                         |
| ------------- | --------------------------------------------------- |
| qbChatMessage | Specifies system message fields that should be set. |

Set the following fields of the `qbChatMessage`:

| Field       | Required | Description                                                                                      |
| ----------- | -------- | ------------------------------------------------------------------------------------------------ |
| recipientId | yes      | ID of the recipient.                                                                             |
| dialogId    | no       | ID of the dialog.                                                                                |
| property    | no       | Extra data. Specify any key-value pairs. In each pair, the key and value are both string values. |
