SyncManager SDKs Android v1
SyncManager SDKs Android
SyncManager SDKs
Android
Version 1

Message sync

Copy link

This page provides an overview of how messages can be synchronized by using the MessageCollection together with other components. The MessageCollection is a view adapter that subscribes to message-related events through the MessageCollectionHandler. The handler listens to cached events and real-time events in order to direct the way of view implementation.

For example, if SyncManager receives a real-time event that a message has been deleted, it creates a collection event which has the remove action for the message and delivers it to the collection handler. At the same time, cached events are also given to the handler. The collection automatically fetches messages from the cache and delivers a collection event which has the insert action for the message to the collection handler.


Initialization

Copy link

The following shows how to initialize and use a MessageCollection instance:

MessageFilter messageFilter = new MessageFilter(BaseChannel.MessageTypeFilter.ALL, null, null);
MessageCollection messageCollection = new MessageCollection(groupChannel, messageFilter, Long.MAX_VALUE);
MessageCollectionHandler messageCollectionHandler = new MessageCollectionHandler() {
    @Override
    public void onMessageEvent(MessageCollection collection, List<BaseMessage> messages, MessageEventAction action) {
        // Deprecated
    }

    @Override
    public void onSucceededMessageEvent(MessageCollection collection, final List<BaseMessage> messages, final MessageEventAction action) {
        switch (action) {
            case INSERT:
                break;
            case REMOVE:
                break;
            case UPDATE:
                break;
            case CLEAR:
                break;
        }
    }

    @Override
    public void onPendingMessageEvent(MessageCollection collection, final List<BaseMessage> messages, final MessageEventAction action) {
        switch (action) {
            case INSERT:
                break;
            case REMOVE:
                break;
        }
    }

    @Override
    public void onFailedMessageEvent(MessageCollection collection, final List<BaseMessage> messages, final MessageEventAction action, final FailedMessageEventActionReason reason) {
        switch (action) {
            case INSERT:
                break;
            case REMOVE:
                break;
            case UPDATE:
                break;
        }
    }

    @Override
    public void onNewMessage(MessageCollection collection, BaseMessage message) {
    }
};

messageCollection.setCollectionHandler(messageCollectionHandler);

// Fetch initial messages.
messageCollection.fetchSucceededMessages(MessageCollection.Direction.PREVIOUS, new FetchCompletionHandler() {
    @Override
    public void onCompleted(boolean hasMore, SendBirdException e) {
        messageCollection.fetchSucceededMessages(MessageCollection.Direction.NEXT, new FetchCompletionHandler() {

            @Override
            public void onCompleted(boolean hasMore, SendBirdException e) {
                if (e != null) {
                    // Handle error.
                }

                messageCollection.fetchFailedMessages(new CompletionHandler() {
                    @Override
                    public void onCompleted(SendBirdException e) {
                        if (e != null) {
                            // Handle error.
                        }

                        ...
                    }
                });
            }
        });
    }
});

In the above code sample, the collection sets the collection handler to listen to message events and call the fetchSucceededMessage() after initialization. At runtime, according to your caching status, the insert event can return multiple times. The first few events are called to fetch the messages from the cache. If not enough messages are cached, SyncManager waits until background sync synchronizes with the server before delivering the rest of the messages. Then, you may get more events with those messages.


Viewpoint

Copy link

A viewpoint is a timestamp option that sets the starting point of the current user’s view. The default value is the Long.MAX_VALUE, which means that the user sees the most recent messages in the chat room. If the viewpoint is set to the middle of the conversation, then the view starts from the middle of the conversation to enable the user to scroll up and down to see the previous and next messages. The viewpoint can be set at initialization, and can be reset anytime by calling the resetViewpointTimestamp() method.

// Set the viewpoint during initialization.
MessageCollection messageCollection = new MessageCollection(groupChannel, messageFilter, Long.MAX_VALUE);
...
messageCollection.resetViewpointTimestamp(Long.MAX_VALUE);


// Fetch initial messages.
messageCollection.fetchSucceededMessages(MessageCollection.Direction.PREVIOUS, new FetchCompletionHandler() {
    @Override
    public void onCompleted(boolean hasMore, SendBirdException e) {
        messageCollection.fetchSucceededMessages(MessageCollection.Direction.NEXT, new FetchCompletionHandler() {
            @Override
            public void onCompleted(boolean hasMore, SendBirdException e) {
                if (e != null) {
                    // Handle error.
                }

                messageCollection.fetchFailedMessages(new CompletionHandler() {
                    @Override
                    public void onCompleted(SendBirdException e) {
                        if (e != null) {
                            // Handle error.
                        }

                        ...
                    }
                });
            }
        });
    }
});

Direction

Copy link

The direction parameter denotes direction and is provided by the MessageCollection’s fetchSucceededMessages(). The collection can fetch messages from two directions: previous and next. Previous messages are fetched in the direction of past messages, while next messages are fetched in the direction of the most recent, real-time messages.


Real-time events

Copy link

SyncManager listens to real-time events from the Chat SDK in order to apply changes and notify the current user. Below is the list of real-time message events that SyncManager listens to through the MessageCollectionHandler's onSucceededMessageEvent() callback method:

Real-time message events

Copy link
EventAction with description

onMessageReceived

INSERT: A new message has been sent. If the view doesn't show the last message, the collection defers the delivery of the event.

onMessageUpdated

UPDATE: A message has been updated. If the collection doesn't have the message in the message list, the event is ignored.

onMessageDeleted

REMOVE: A message has been deleted. If the collection doesn't have the message in the message list, the event is ignored.

Note: The collection has an array that holds all the messages that should be shown in the view. If a message isn’t shown in the view, it means that the collection isn’t holding it because background sync synchronization hasn’t been completed at that time.


fetchSuccededMessages()

Copy link

The fetchSucceededMessages() method fetches messages from the local cache and delivers them to the handler. If not enough messages are cached, it waits until background sync synchronizes with the server before bringing the retrieved messages. The fetchSucceededMessages() should be called when:

  • A collection is created.
  • A connection is established.
  • The app goes to the foreground.
  • The scroll reaches the top or the bottom.
  • The app returns from a locked screen when still in the foreground.

The fetchSucceededMessages() subscribes to background sync to pull data from the local cache. As this method doesn’t directly interact with the server, while online, it waits until background sync synchronizes data in Sendbird server to the cache before retrieving new data. However, after synchronization is completed by background sync, the fetchSucceededMessages() can refresh the view with new data. Therefore, the fetchSucceededMessages() can not only read cached data, but also update data in the view.

The fetchAllNextMessages() behaves similarly to fetchSucceededMessages(Direction.NEXT), except it fetches the most recent messages, regardless of the fetch limit set in the MessageCollection instance. This method can be used to fetch messages when a user, who had scrolled to the bottom of the screen and was looking at the most recent messages, comes online.

The method to fetch messages when a user comes online from offline should depend on their scroll point. Regardless of whether the user is connecting to a network, coming to the foreground from the background, or turning on their screen, this should be implemented in the ConnectionHandler, which listens for state changes from offline to online.

@Override
public void onResume() {
    super.onResume();

    SendBird.addConnectionHandler(YOUR_CONNECTION_HANDLER_ID, new SendBird.ConnectionHandler() {
        @Override
        public void onReconnectStarted() {
            ...
        }

        @Override
        public void onReconnectSucceeded() {
            if (layoutManager.findFirstVisibleItemPosition() <= 0) {
                // Fetch all next messages if a user was viewing the most recent message.
                messageCollection.fetchAllNextMessages(new FetchCompletionHandler() {
                    @Override
                    public void onCompleted(boolean hasMore, SendBirdException e) {
                        if (e != null) {
                            // Handle error.
                        }

                        ...
                    }
                });
            }

            if (layoutManager.findLastVisibleItemPosition() == chatAdapter.getItemCount() - 1) {
                // Fetch previous messages if a user was viewing the oldest message.
                messageCollection.fetchSucceededMessages(MessageCollection.Direction.PREVIOUS, new FetchCompletionHandler() {
                    @Override
                    public void onCompleted(boolean hasMore, SendBirdException e) {
                        if (e != null) {
                            // Handle error.
                        }

                        ...
                    }
                });
            }
        }

        @Override
        public void onReconnectFailed() {
            ...
        }
    });
}

@Override
public void onPause() {
    super.onPause();

    SendBird.removeConnectionHandler(YOUR_CONNECTION_HANDLER_ID);
}

Message lifecycle

Copy link

Once a message is created, it has its own lifecycle. When a message has been sent but not yet received any response from Sendbird server through its callback, the requestState of the message is pending. Once the server responds, the requestState of the message is set as succeeded or failed. SyncManager internally keeps track of the request state of a message so as to update the view accordingly.

For each requestState, a message in a view can be displayed as follows:

  • pending: displays the message content with a progress animation.
  • failed: displays the message content with a context menu such as resend or delete.
  • succeeded: displays the message content with its read receipt.

Note: Pending and failed messages temporarily have a messageId of 0. When they are successfully sent to the server, they get their own unique message ID, and are delivered and replaced as succeeded messages in the collection handler by their reqId. The purpose of a reqId is to ensure messages are listed in the order of their time sent.


Send a message

Copy link

As messages are sent by the Chat SDK, SyncManager cannot check whether a message has been successfully sent. Therefore, the MessageCollection provides the handleSendMessageResponse() method so that SyncManager recognizes the delivery status.

UserMessage pendingMessage = groupChannel.sendUserMessage(MESSAGE, new BaseChannel.SendUserMessageHandler() {
    @Override
    public void onSent(UserMessage userMessage, SendBirdException e) {
        if (e != null) {
            // Handle error.
        }

        if (messageCollection != null) {
            messageCollection.handleSendMessageResponse(userMessage, e);
            messageCollection.fetchAllNextMessages(null);
        }
    }
});

if (messageCollection != null) {
    messageCollection.appendMessage(pendingMessage);
}

The handleSendMessageResponse() method conducts different jobs based on the set message resend policy. Below is the the list of acceptable values:

Acceptable values

Copy link
ValueDescription

none

If delivery succeeded: performs the remove event for the pending message and the insert event for the succeeded message.
If delivery failed: performs the remove event for the pending message.

manual

If delivery succeeded: performs the remove event for the pending message and the insert event for the succeeded message.
If delivery failed: performs the remove event for the pending message and the insert event for the failed message. The failed message can be manually resent by calling the resendUserMessage() and resendFileMessage() in a SendBird instance.

automatic

If delivery succeeded: performs the remove event for the pending event and the insert event for the succeeded message.
If delivery failed: performs the remove event for the pending message and the insert event for the failed message. Automatically resends the failed message.


Update a message

Copy link

As previously mentioned, SyncManager can't check whether a message has been successfully sent because messages are sent through the Chat SDK. Therefore, the MessageCollection provides the updateMessage() method so that SyncManager recognizes the delivery status.

groupChannel.updateUserMessage(message.getMessageId(), UPDATED_MESSAGE, null, null, new BaseChannel.UpdateUserMessageHandler() {
    @Override
    public void onUpdated(UserMessage userMessage, SendBirdException e) {
        if (e != null) {
            // Handle error.
        }

        if (messageCollection != null) {
            messageCollection.updateMessage(userMessage);
        }
    }
});

Note: Only succeeded messages can be updated.


Remove a message

Copy link

Messages can be deleted using the deleteMessage() method in the Chat SDK.

groupChannel.deleteMessage(message, new BaseChannel.DeleteMessageHandler() {
    @Override
    public void onResult(SendBirdException e) {
        if (e != null) {
            // Handle error.
        }

        if (messageCollection != null) {
            messageCollection.deleteMessage(message);
        }

        // The collection handler gets the 'remove' event for the message, so no further job is required.
    }
});

Handle a failed message

Copy link

According to the message's requestState, the MessageCollectionHandler instance listens to message events through the following callbacks.

Message event callbacks

Copy link
Event callbackMessage delivery status & how SyncManager handles a message

onSucceededMessageEvent()

succeeded: A message has been successfully sent to Sendbird server and automatically stored in the local cache.

onPendingMessageEvent()

pending: The Chat SDK's method has sent a message, but not yet received any response from Sendbird server through its callback. SyncManager will wait for a certain period of time to define the delivery status of the message by either succeeded or failed. pending refers to when the message remains unprocessed and SyncManager is waiting for a response.

onFailedMessageEvent()

failed: A message has failed to be sent to Sendbird server and the Chat SDK's method receives an error through its callback. The message should be either appended to or deleted from the MessageCollection instance using the appendMessage() or deleteMessage() method.
If the messageResendPolicy is automatic, SyncManager will attempt to resend the message automaticMessageResendRetryCount times. Otherwise, you should resend the message by manually calling the Chat SDK's resendUserMessage() method.

Suppose that a new message is requested and successfully sent. In order for this to happen, the insert event should first be given to the onPendingMessageEvent(). Once the message delivery status is determined as succeeded through the callback, the remove event is given to the onPendingMessageEvent() while the insert event is also given to the onSucceedMessageEvent().


Fetch pending messages

Copy link

A pending message is a message that hasn't been sent or hasn't been resolved as a success or a failure. A pending message is created when the app is closed before the message is resolved. Then SyncManager saves the message state and resolves it later.

Background sync

Copy link

SyncManager resolves pending messages through a background sync. It is a process where SyncManager compares and matches message data in the local cache with that in Sendbird server.

SyncManager resolves pending messages in the local cache as a success when it confirms matching messages in Sendbird server. If not, SyncManager resolves them as a failure.

The fetchPendingMessages() method fetches pending messages from the local cache. Depending on their message state, different message events are delivered to different callback methods.

Message events

Copy link
Message stateEventCallback method

Pending messages exist

MessageEventAction.INSERT

onPendingMessageEvent()

Pending messages are successfully sent

MessageEventAction.INSERT

onSucceededMessageEvent()

Pending messages definitely failed

MessageEventAction.INSERT

onFailedMessageEvent()

Pending messages are resolved

MessageEventAction.REMOVE

onPendingMessageEvent()

Note: It is recommended that the fetchPendingMessages() method be called after the MessageCollection instantiation so that all pending message events create before the instantiation can also be added to the MessageCollection instance.

MessageCollection.fetchPendingMessages(new CompletionHandler() {
    @Override
    public void onCompleted(SendBirdException e) {
        if (e != null) {
            // Handle error.
        }

        // Pending messages are successfully fetched.
        ...
    }
});

Close the view

Copy link

When the message list view is closed, the MessageCollection instance should be cleared. A MessageCollection has the remove() method, which clears all the messages managed by the collection and stops synchronization processes in the collection instance. If the collection instance isn’t explicitly dropped, the applicable memory won’t be released and could lead to performance slowdown.

@Override
public void onDestroy() {
    if (messageCollection != null) {
        messageCollection.setCollectionHandler(null);
        messageCollection.remove();
    }

    super.onDestroy();
}