Chat SDKs Android v3
Chat SDKs Android
Chat SDKs
Android
Version 3
Sendbird Chat SDK v3 for Android is no longer supported as a new version is released. Check out our latest Chat SDK v4

Local caching

Copy link

Local caching enables Sendbird Chat SDK for Android to locally cache and retrieve group channel and message data. Its benefits include reducing refresh time and allowing a client app to create a channel list or a chat view that can work online as well as offline, which can be used for offline messaging.

Note: You can enable local caching when initializing the Chat SDK by setting the optional parameter useLocalCaching to true. See Initialize the Chat SDK and Connect to Sendbird server in the Authentication page to learn more.

Local caching relies on the GroupChannelCollection and MessageCollection classes, which are used to build a channel list view and a chat view, respectively.

Note: You can still use these collections when building your app without local caching.

Unlike SyncManager that listens to any data update, collections are designed to react to the events that can cause change in a view. An Event controller in the SDK oversees such events and passes them to a relevant collection. For example, if a new group channel is created while the current user is looking at a chat view, the current view won't be affected by this event.

Note: Understand the differences between local caching and SyncManager, and learn how the Migration should be done.


GroupChannelCollection

Copy link

A GroupChannelCollection is composed of the following methods and variables.

// Create a GroupChannelListQuery object first.
private void createGroupChannelCollection() {
    GroupChannelListQuery query = GroupChannel.createMyGroupChannelListQuery();
    query.setIncludeEmpty(true);
    query.setOrder(GroupChannelListQuery.Order.CHRONOLOGICAL);

    // Create a GroupChannelCollection instance and set the event handlers.
    final GroupChannelCollection collection = new GroupChannelCollection.Builder(query).build();

    collection.setGroupChannelCollectionHandler(new GroupChannelCollectionHandler() {
        @Override
        public void onChannelsAdded(@NonNull GroupChannelContext context, @NonNull List<GroupChannel> channels) {
        }

        @Override
        public void onChannelsUpdated(@NonNull GroupChannelContext context, @NonNull List<GroupChannel> channels) {
        }

        @Override
        public void onChannelsDeleted(@NonNull GroupChannelContext context, @NonNull List<String> deletedChannelUrls) {
        }
    });
}

// Load channels.
private void loadMore() {
    if (collection.hasMore()) {
        collection.loadMore(new OnChannelLoadResultHandler() {
            @Override
            public void onResult(@Nullable List<GroupChannel> channels, @Nullable SendBirdException e) {
                if (e != null) {
                    // Handle error.
                    return;
                }

            }
        });
    }
}

// Clear the collection.
private void dispose() {
    collection.dispose();
}

When the createGroupChannelCollection() method is called, the group channel data stored in the local cache and Sendbird server are fetched and sorted based on the values in GroupChannelListQuery. Also, setGroupChannelCollectionHandler() allows you to set the event listeners that can subscribe to channel-related events when creating the collection.

As for pagination, hasMore() will check if there are more group channels to load whenever a user hits the bottom of the channel list. If true, loadMore() will retrieve channels from the local cache and the remote server to show on the list.

To learn more about the collection and how to create a channel list view with it, see Group channel collection.


MessageCollection

Copy link

A MessageCollection is composed of the following methods and variables.

private void createMessageCollection() {
    // You can use a MessageListParams instance for the MessageCollection.
    MessageListParams params = new MessageListParams();
    params.setReverse(false);
    ...

    final MessageCollection collection = new MessageCollection.Builder(groupChannel, params)
        .setStartingPoint(startingPoint)
        .build();

    collection.setMessageCollectionHandler(new MessageCollectionHandler() {
        @Override
        public void onMessagesAdded(@NonNull MessageContext context, @NonNull GroupChannel channel, @NonNull List<BaseMessage> messages) {
            switch (context.getMessagesSendingStatus()) {
                case SUCCEEDED:
                    break;
                case PENDING:
                    break;
            }
        }

        @Override
        public void onMessagesUpdated(@NonNull MessageContext context, @NonNull GroupChannel channel, @NonNull List<BaseMessage> messages) {
            switch (context.getMessagesSendingStatus()) {
                case SUCCEEDED:
                    break;
                case PENDING: (failed -> pending)
                    break;
                case FAILED: (pending -> failed)
                    break;
                case CANCELED: (pending -> canceled)
                    break;
            }
        }

        @Override
        public void onMessagesDeleted(@NonNull MessageContext context, @NonNull GroupChannel channel, @NonNull List<BaseMessage> messages) {
            switch (context.getMessagesSendingStatus()) {
                case SUCCEEDED:
                    break;
                case FAILED:
                    break;
            }
        }

        @Override
        public void onChannelUpdated(@NonNull GroupChannelContext context, @NonNull GroupChannel channel) {

        }

        @Override
        public void onChannelDeleted(@NonNull GroupChannelContext context, @NonNull String channelUrl) {

        }

        @Override
        public void onHugeGapDetected() {
            // This is called when there are more than 300 messages missing from the local cache
            // when compared to the number of messages in the remote server.

            // Clear the collection.
            collection.dispose();

            // Create a new message collection object.
            createMessageCollection();

            // An additional implementation is required for initialization.
        }
    });
}

// Initialize messages from the startingPoint.
private void initialize() {
    collection.initialize(MessageCollectionInitPolicy.CACHE_AND_REPLACE_BY_API, new MessageCollectionInitHandler() {
        @Override
        public void onCacheResult(@Nullable List<BaseMessage> cachedList, @Nullable SendBirdException e) {
            // Messages will be retrieved from the local cache.
        }

        @Override
        public void onApiResult(@Nullable List<BaseMessage> apiResultList, @Nullable SendBirdException e) {
            // Messages will be retrieved from Sendbird server through API.
            // According to the InitPolicy.CACHE_AND_REPLACE_BY_API,
            // the existing data source needs to be cleared
            // before adding retrieved messages to the local cache.
        }
    });
}

// Load next messages.
private void loadNext() {
    if (collection.hasNext()) {
        collection.loadNext(new BaseChannel.GetMessagesHandler() {
            @Override
            public void onResult(List<BaseMessage> messages, SendBirdException e) {
                if (e != null) {
                    // Handle error.
                    return;
                }
            }
        });
    }
}

// Load previous messages.
private void loadPrevious() {
    if (collection.hasPrevious()) {
        collection.loadPrevious(new BaseChannel.GetMessagesHandler() {
            @Override
            public void onResult(List<BaseMessage> messages, SendBirdException e) {
                if (e != null) {
                    // Handle error.
                    return;
                }
            }
        });
    }
}

// Clear the collection.
private void dispose() {
    collection.dispose();
}

In a MessageCollection, the initialization is dictated by MessageCollectionInitPolicy. This determines which data to use for the collection. Currently, we only support CACHE_AND_REPLACE_BY_API. According to this policy, the collection will load the messages stored in the local cache through onCachedResult(). Then onApiResult() will replace them with the messages fetched from Sendbird server through an API request.

As for pagination, hasNext() will check if there are more messages to load whenever a user hits the bottom of the chat view. If true, the loadNext() method will retrieve messages from the local cache to show in the view. loadPrevious() works the same way when the scroll reaches the top of the view.

To learn more about the collection and how to create a chat view with it, see Message collection.


Gap and synchronization

Copy link

A gap is created when messages or channels that exist in Sendbird server are missing from the local cache. Such discrepancy occurs when a client app isn't able to properly load new events due to issues with the connection status.

To prevent such a gap, the Chat SDK constantly communicates with Sendbird server and fetches data through background sync. The synchronization pulls the data from the latest to the oldest. This ensures that the local cache has all the data in order. However, because this synchronization takes place in the background, changes won't call collection event handlers or affect the view.

Despite background sync, a gap may still be created. When the client app is in the foreground, the SDK checks with Sendbird server to see if there has been a gap. If more than 300 messages are missing in the local cache compared to the remote server, Sendbird Chat SDK classifies this as a huge gap and onHugeGapDetected() is called. In case of a huge gap, it is more effective to discard the existing message collection and create a new one. On the other hand, a relatively small gap can be filled in through changelog sync.

Changelog sync

Copy link

While background sync ensures the local cache to keep its data up-to-date with Sendbird server, changelog sync fetches any event that can show in the channel list view and the chat view.

Changelog sync pulls data when the client app is back online. When the client app is connected to the server, it fetches events in chronological order of the updated_at value, from first to last. Events fetched by changelog sync will be added to the collection, updating the view.

The events will be delivered to the collection through event handlers such as onMessageUpdated(), onMessageDeleted(), onChannelUpdated(), onChannelDeleted(), and onChannelAdded().


Auto resend

Copy link

A message is normally sent through the WebSocket connection which means the connection must be secured and confirmed before sending any message. With local caching, you can temporarily keep an unsent message in local cache if the WebSocket connection is lost. The Chat SDK with local caching marks the failed message as pending, locally stores it, and automatically resends the pending message when the WebSocket is reconnected. This is called auto resend.

The following cases are eligible for auto resend when:

  • A user message couldn't be sent because the WebSocket connection is lost even before it is established.

  • A file message couldn't upload the attached file to Sendbird server.

  • An attached file was uploaded to Sendbird server but the file message itself couldn't be sent because the WebSocket connection is closed.

User message

Copy link

A user message is sent through the WebSocket. If a message couldn't be sent because the WebSocket connection is lost, the Chat SDK will receive an error through a callback and queue the pending message in the local cache for auto resend. When the client app is reconnected, the SDK will attempt to resend the message.

If the message is successfully sent, the client app will receive a response from Sendbird server. Then onMessageUpdated() is called to change the pending message to a sent message in the data source and updates the chat view.

File message

Copy link

A file message can be sent through either the WebSocket connection or an API request.

When sending a file message, the attached file must be uploaded to Sendbird server as an HTTP request. To do so, the Chat SDK checks the status of the network connection. If the network is not connected, the file can't be uploaded to the server. In this case, the SDK will handle the file message as a pending message and put it in a queue for auto resend.

If the network is connected and the file is successfully uploaded to the server, its URL will be delivered in a response and the SDK will replace the file with its URL string. At first, the SDK attempts to send the message through the WebSocket. If the WebSocket connection is lost, the client app checks the network connection once again to make another HTTP request for the message. If the SDK detects the network as disconnected, it will get an error code that marks the message as a pending message, allowing the message to be automatically resent when the network is reconnected.

On the other hand, if the network is connected but the HTTP request fails, the message isn't eligible for auto resend.

If the message is successfully sent, the client app will receive a response from Sendbird server. Then onMessageUpdated() is called to change the pending message to a sent message in the data source and updates the chat view.

Failed message

Copy link

If a message couldn't be sent due to other error cases, onMessageUpdated() will be called to re-label the pending message as a failed message. Such message can't be queued for auto resending. The following show some of those errors.

SendBirdError.ERR_NETWORK
SendBirdError.ERR_ACK_TIMEOUT

Note: A pending message can last in the queue only for three days. If the WebSocket connection is back online after three days, onMessageUpdated() will be called to mark any 3-day-old pending message as a failed message.


Other methods

Copy link

The following shows a list of methods that can help you leverage the local caching functionalities.

getCachedDataSize() will let you know the size of the data stored in the local cache. If you want to erase the whole data, use clearCachedData(). Meanwhile, clearCachedMessages() allows you to get rid of unnecessary messages from the local cache by specifying the channel URL of those messages.

getMessageParams() is used when pulling up pending messages in the chat view. This method will return the exact same UserMessageParams or FileMessageParams that you used when sending the messages. The params can be passed to the following methods.

  • channel.sendUserMessage(UserMessageParams, SendUserMessageHandler)

  • channel.sendFileMessage(FileMessageParams, SendFileMessageHandler)

SendBird.getCachedDataSize(): Long
SendBird.clearCachedData(@NonNull final Context context, @Nullable final CompletionHandler handler)
SendBird.clearCachedMessages(@NonNull final String channelUrl, @Nullable final CompletionHandler handler)

BaseMessage.getMessageParams(): BaseMessageParams   // This is used for auto resend or when creating a new collection due to a huge gap.