Chat / Android
Chat Android v4
Chat Android
Chat
Android
Home
/
Chat
/
Android
This is the new Docs for Chat SDK v4 for Android. To see the previous Docs, click here.

Local caching

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.

You can enable local caching when initializing the Chat SDK by setting the optional parameter useCaching to true. See Initialize the Chat SDK and Connect to the Sendbird server 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. Collections are designed to react to events that can cause change in a channel list or chat 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: You can still use these collections when building your app without local caching.


Functionalities by topic

The following is a list of functionalities our Chat SDK supports.

Using group channel collection

FunctionalityDescriptionOpen channelGroup channel

Group channel collection

Keeps the client app's channel list synced with that on the Sendbird server.

Using message collection

FunctionalityDescriptionOpen channelGroup channel

Message collection

Keeps the client app's chat view synced with that on the Sendbird server.


GroupChannelCollection

You can use GroupChannelCollection to swiftly create a channel list view that stays up to date on all channel-related events. A GroupChannelCollection instance is composed of the following methods and variables.

// Create a GroupChannelListQuery object first.
val query = GroupChannel.createMyGroupChannelListQuery(
    GroupChannelListQueryParams().apply {
        includeEmpty = true
        order = GroupChannelListQueryOrder.CHRONOLOGICAL
    }
)
val params = GroupChannelCollectionCreateParams(query).apply {
    groupChannelCollectionHandler =
        object : GroupChannelCollectionHandler {
            override fun onChannelsAdded(context: GroupChannelContext, channels: List<GroupChannel> ) {}

            override fun onChannelsUpdated(context: GroupChannelContext, channels: List<GroupChannel>) {}

            override fun onChannelsDeleted(context: GroupChannelContext, deletedChannelUrls: List<String>) {}
        }
}
// Create a GroupChannelCollection instance and set the event handlers.
val collection = SendbirdChat.createGroupChannelCollection(params)

// Load channels.
if (collection.hasMore) {
    collection.loadMore { channels, e ->
        if (e != null) {
            // Handle error.
            return@loadMore
        }
    }
}

// Clear the collection.
collection.dispose()

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

As for pagination, hasMore checks if there are more group channels to load whenever a user hits the bottom of the channel list. If so, loadMore() retrieves channels from the local cache and the Sendbird server to display on the list.

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


MessageCollection

You can use MessageCollection to swiftly create a chat view that includes all data. A MessageCollection instance is composed of the following methods and variables.

fun createMessageCollection() {
    // You can use a MessageListParams instance for MessageCollection.
    val params = MessageListParams().apply {
        reverse = false
        // ...
    }

    val messageCollectionHandler = object : MessageCollectionHandler {
        override fun onMessagesAdded(
            context: MessageContext,
            channel: GroupChannel,
            messages: List<BaseMessage>
        ) {
            when (context.messagesSendingStatus) {
                SendingStatus.SUCCEEDED -> {}
                SendingStatus.PENDING -> {}
            }
        }

        override fun onMessagesUpdated(
            context: MessageContext,
            channel: GroupChannel,
            messages: List<BaseMessage>
        ) {
            when (context.messagesSendingStatus) {
                SendingStatus.SUCCEEDED -> {}
                SendingStatus.PENDING -> {} // failed -> pending
                SendingStatus.FAILED -> {} // pending -> failed
                SendingStatus.CANCELED -> {} // pending -> canceled
            }
        }

        override fun onMessagesDeleted(
            context: MessageContext,
            channel: GroupChannel,
            messages: List<BaseMessage>
        ) {
            when (context.messagesSendingStatus) {
                SendingStatus.SUCCEEDED -> {}
                SendingStatus.FAILED -> {}
            }
        }

        override fun onChannelUpdated(context: GroupChannelContext, channel: GroupChannel) {}

        override fun onChannelDeleted(context: GroupChannelContext, channelUrl: String) {}

        override fun onHugeGapDetected() {
            // This is called when there are more than 300 messages missing in the local cache
            // compared to the Sendbird server.

            // Clear the collection.
            collection.dispose()

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

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

    val collection = SendbirdChat.createMessageCollection(
        MessageCollectionCreateParams(groupChannel, params).apply {
            this.startingPoint = STARTING_POINT
            this.messageCollectionHandler = messageCollectionHandler
        }
    )
}

// Initialize messages from startingPoint.
fun initialize() {
    collection.initialize(
        MessageCollectionInitPolicy.CACHE_AND_REPLACE_BY_API,
        object : MessageCollectionInitHandler {
            override fun onCacheResult(cachedList: List<BaseMessage>?, e: SendbirdException?) {
                // Messages are retrieved from the local cache.
            }

            override fun onApiResult(apiResultList: List<BaseMessage>?, e: SendbirdException?) {
                // Messages are retrieved from the 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 the next set of messages.
fun loadNext() {
    if (collection.hasNext) {
        collection.loadNext { messages, e ->
            if (e != null) {
                // Handle error.
                return@loadNext
            }
        }
    }
}

// Load previous messages.
fun loadPrevious() {
    if (collection.hasPrevious) {
        collection.loadPrevious { messages, e ->
            if (e != null) {
                // Handle error.
                return@loadPrevious
            }
        }
    }
}

// Clear the collection.
fun dispose() {
    collection.dispose()
}

In the MessageCollection class, 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 loads the messages stored in the local cache through onCachedResult(). Then onApiResult() replaces them with the messages fetched from the Sendbird server through an API request.

As for pagination, hasNext checks if there are more messages to load whenever a user hits the bottom of the chat view. If so, the loadNext() method retrieves messages from the local cache to display in the view. hasPrevious and loadPrevious() work the same way when the scroll reaches the top of the chat view.

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


Gap and synchronization

A gap is created when messages or channels that exist on the 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 connectivity issues.

To prevent such a gap, the Chat SDK constantly communicates with the Sendbird server and fetches data through background sync. The synchronization process pulls the data from most to least recent. 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 the background sync, a gap may still be created. When the client app is in the foreground, the SDK checks with the Sendbird server to see if there has been a gap. If more than 300 messages are missing in the local cache compared to the Sendbird 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

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

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

The events are delivered to the collection by event handlers such as GroupChannelCollectionHandler.onChannelsAdded(), GroupChannelCollectionHandler.onChannelsUpdated(), GroupChannelCollectionHandler.onChannelsDeleted(), MessageCollectionHandler.onMessagesAdded(), MessageCollectionHandler.onMessagesUpdated(), MessageCollectionHandler.onMessagesDeleted(), MessageCollectionHandler.onChannelUpdated(), and MessageCollectionHandler.onChannelDeleted().


Auto resend

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

The following cases are eligible for auto resend.

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

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

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

User message

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

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

File message

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 the Sendbird server as an HTTP request. To do so, the Chat SDK checks the status of the network connection. If the network isn't connected, the file can't be uploaded to the server. In this case, the SDK handles the file message as a pending message and adds to the queue for auto resend.

If the network is connected and the file is successfully uploaded to the server, its URL is delivered in a response and the SDK replaces 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 gets 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 receives a response from the Sendbird server. Then onMessagesUpdated() is called to change the pending message to a sent message in the data source and updates the chat view.

Failed message

If a message couldn't be sent due to some other error, onMessagesUpdated() is called to re-label the pending message as a failed message. Messages labeled as such can't be queued for auto resend. The following shows some of these 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, onMessagesUpdated() is called to mark any three-day-old pending message as failed messages.


Other methods

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

The SendbirdChat.getCachedDataSize() method lets you know the size of the data stored in the local cache. If you want to erase the entire data, use the SendbirdChat.clearCachedData() method. Meanwhile, the SendbirdChat.clearCachedMessages() method lets you get rid of unnecessary messages from the local cache by specifying the channel URL of those messages.

The BaseMessage.messageCreateParams property is used when drawing pending messages in the chat view. This method returns the exact same UserMessageCreateParams or FileMessageCreateParams that you used when sending messages. The params can be passed to the following methods.

  • channel.sendUserMessage(UserMessageCreateParams, SendUserMessageHandler)

  • channel.sendFileMessage(FileMessageCreateParams, SendFileMessageHandler)

SendbirdChat.getCachedDataSize(Context): Long
SendbirdChat.clearCachedData(context: Context, handler: CompletionHandler?)
SendbirdChat.clearCachedMessages(channelUrl: String, handler: CompletionHandler?)

BaseMessage.messageCreateParams: BaseMessageCreateParams?   // This should be used for displaying pending messages in the chat view.