In-app Chat
SDK Error Codes
On this page

Render Conversation Messages on the Chat Page

2026-04-21

Overview

This article describes how to use the ZIM SDK to render conversation messages on a basic one-on-one chat page.

showMessage.png

Prerequisites

You have already integrated the ZIM SDK into your project and implemented basic message sending and receiving. For details, refer to Quick Start - Implement Basic Message Sending and Receiving.

Message Data Source Overview

The message data sources that need to be rendered on the page mainly include the following:

  1. Historical conversation messages: When you first enter the chat page and view historical messages, you need to query historical conversation messages and render them to the chat interface.
  2. Real-time received messages: Real-time received chat messages need to be rendered to the chat interface.
  3. Local sent messages: Local sent chat messages (including sending, sending successfully, and sending failed statuses) need to be rendered to the chat interface.

Get Message Data and Render to the Chat Interface

As an example of a single chat conversation, when you navigate to the chat page, you need to save the corresponding single chat conversationID (the userID of the other party) and maintain a message list myMessageList for saving the current conversation.

Before obtaining message data, you also need to implement an addMessage utility method to merge message data.

public void addMessage(List<ZIMMessage> addList) {
    if (addList == null || addList.isEmpty()) return;

    List<ZIMMessage> mutableList = new ArrayList<>();
    if (myMessageList != null) {
        mutableList.addAll(myMessageList);
    }

    for (ZIMMessage newMsg : addList) {
        boolean replaced = false;
        for (int i = 0; i < mutableList.size(); i++) {
            ZIMMessage oldMsg = mutableList.get(i);
            if ((newMsg.getMessageID() != null && newMsg.getMessageID().equals(oldMsg.getMessageID())) ||
                (newMsg.getLocalMessageID() != null && newMsg.getLocalMessageID().equals(oldMsg.getLocalMessageID()))) {
                mutableList.set(i, newMsg);
                replaced = true;
                break;
            }
        }
        if (!replaced) {
            mutableList.add(newMsg);
        }
    }

    mutableList.sort(Comparator.comparingLong(ZIMMessage::getOrderKey));
    myMessageList = mutableList;
}
Note

Each time you call a message-related interface (such as the interfaces shown below), you will get a message list ZIMMessage[]. The obtained message list needs to be merged to obtain a message list without duplicate messages and ordered messages for rendering on the message page. The message sorting rule is to sort by the orderkey property in ZIMMessage (the larger the orderkey, the newer the current message); the de-duplication rule is to determine whether the message object is the same message based on the localMessageID of the message, and replace the new data with the old data when it is duplicated.

Query Historical Messages and Render Them to the Chat Interface

Call the queryHistoryMessage interface to query historical messages from new to old (from the most recent message to the oldest message in chronological order) and render them to the chat interface.


ZIMMessageQueryConfig queryConfig = new ZIMMessageQueryConfig();
queryConfig.count = 20;
queryConfig.nextMessage = null; // When the first query is empty, the earliest message is passed in when more historical messages are needed

zim.queryHistoryMessage(
        myConversationID,
        ZIMConversationType.PEER,
        queryConfig,
        (conversationID, conversationType, messageList, errorInfo) -> {
            addMessage(messageList);
            // update UI
        });

Get Real-time Received Messages and Render Them to the Chat Interface

Listen to the onPeerMessageReceived interface to get real-time received messages and render them to the chat interface.

 public void onPeerMessageReceived(ZIM zim, List<ZIMMessage> messageList, 
                                  ZIMMessageReceivedInfo info, String fromUserID) {
    // Only process messages from the current conversation
    if (!fromUserID.equals(myConversationID)) {
        return; // Messages from non-current conversations are ignored
    }

    // Merge messages into message list
    addMessage(messageList);

    // Update UI, such as refreshing RecyclerView
    runOnUiThread(() -> {
        myAdapter.notifyDataSetChanged();
        if (!myMessageList.isEmpty()) {
            recyclerView.scrollToPosition(myMessageList.size() - 1);
        }
    });
}

Render Local Sent Messages to the Chat Interface

Listen to the onMessageSentStatusChanged interface to get the change of the sending status of the local sent message (sending, sending successfully, and sending failed), and render the local sent message and its sending status to the chat interface.


@Override
public void onMessageSentStatusChanged(List<ZIMMessageSentStatusChangeInfo> messageSentStatusChangeInfoList) {
    for (ZIMMessageSentStatusChangeInfo info : messageSentStatusChangeInfoList) {
        if (!info.getMessage().getConversationID().equals(myConversationID)) {
            continue;
        }
        addMessage(Collections.singletonList(info.getMessage()));
    }
    // Update UI
    runOnUiThread(() -> {
        myAdapter.notifyDataSetChanged();
        if (!myMessageList.isEmpty()) {
            recyclerView.scrollToPosition(myMessageList.size() - 1);
        }
    });
}

By following the above steps, you can render historical messages, real-time received messages, and local sent messages (including sending status) in the chat page.

FAQ

Message data may change during its lifecycle. Common scenarios include:

  1. The sending status changes from "sending" to "sent" or "failed".
  2. The message is recalled, causing the message type to change (e.g., from a text message to a recalled message).
  3. A sent message is edited.

The SDK pushes the latest message object when any of the above changes occur. To maintain message data consistency, you should replace the existing message with the latest message object and refresh the UI.

messageID is assigned by the server and has global uniqueness, serving as the primary identifier of a message.

In the following scenarios, a message may not yet have a messageID:

  • The message is being sent
  • The message failed to send
  • The message is only saved locally (inserted via insertMessageToLocalDB)

In these cases, localMessageID is used as a temporary client-side identifier. The matching rule is: use messageID when available, otherwise use localMessageID.

By combining these two identifiers, you can accurately determine whether two message objects represent the same message at any stage of the message lifecycle.

timestamp reflects the time the message reached the server, while orderKey is specifically designed for message sorting and provides consistent sorting results.

When messages are sent rapidly in succession or under unstable network conditions, timestamp may deviate, causing message ordering issues. orderKey takes into account both the global message order and the sending order of the same sender, ensuring that messages are displayed consistently with the conversation flow.

Sorting by orderKey ensures:

  • Stable sorting results
  • Correct conversation order
  • Consistent display across different clients

ZIM already sorts messages by orderKey in the results returned by each API or event.

In practice, the chat page message list is composed of multiple data sources merged together:

  • Historical messages (queryHistoryMessage)
  • Real-time received messages (onPeerMessageReceived)
  • Message updates (status changes, recalls, edits, etc.)

The client is the only layer that can access all data sources simultaneously. After merging, it needs to uniformly re-sort by orderKey to ensure:

  • Stable and correct message order
  • Coherent conversation flow
  • Unified view across all data sources

ZIM is responsible for internal ordering within each data source, while the client is responsible for global sorting consistency after merging.

Previous

Modify a notification badge

Next

Subscribe to user online status