Home
/
Chat
/
Android
/
Local caching

Migration from SyncManager

Local caching can speedily access the data from the local cache and Sendbird server than the existing SyncManager for Android that supported the synchronization of chat data for your Android client app. It is highly recommended to migrate from SyncManager to the Chat SDK with local caching for its enhanced usability and easier maintenance.

With SyncManager, event handlers were called even when an event that is irrelevant to the user's view takes place. Local caching provides an event context and a callback to help identify which events are happening where, making it easier to locate and debug a problem.

The following table shows the differences between local caching and SyncManager.

Local cachingSyncManager

Implementation

Integrated to Chat SDK

Add-on to Chat SDK

Architecture

View-driven

Event-driven

Synchronization

Changelog sync and background sync

Background sync

Auto resend

Supported

Not supported

Furthermore, SyncManager is based on an event-driven architecture, where all events received through the WebSocket connection as well as the data in the local cache are delivered to the client app by SyncManager's collection handlers. On the other hand, local caching's collections feature a view-driven architecture which is designed to only react to events that affect the user's channel list and chat view.

Lastly, with local caching, you can temporarily keep failed messages in the local cache if the WebSocket connection is lost. The Chat SDK with local caching marks the failed messages as pending, locally stores them, and automatically resends the pending messages when the WebSocket is reconnected. This is called auto resend, a new functionality that local caching supports.

Note: SyncManager will continue to work if the dependency to SyncManager is not removed. The dependency should be deleted once the migration to local caching is completed.


Initialization

There are two ways to initialize the Chat SDK, with local caching or without local caching. During the initialization, the useLocalCaching determines whether the client app will use local caching with Sendbird Chat SDK or not. Its default value is false meaning the Chat SDK will be initialized without local caching.


In local caching, the InitResultHandler() is added to the SendBird.init() method, which receives the initialization status through different event handlers. The onMigrationStarted() is called when there's a change in the local cache. Meanwhile, the onInitSucceeded() and the onInitFailed() informs the client app of whether the initialization is successful or not.

Note: If initialization fails to complete and the onInitFailed() is called while the useLocalCaching is set to true, Chat SDK will operate as if the useLocalCaching is set to false.

Connect to Sendbird server

The difference between local caching and SyncManager in terms of the SendBird.connect() is whether it is necessary to call the SendBird.connect() during initialization or not. Both local caching and SyncManager ensure that your client app is operational during offline mode. However, to use the offline mode in SyncManager, calling the SendBird.connect() during the initialization process was optional. Instead, you only had to call the SyncManager.setup() after the SendBird.init(). In local caching, however, calling the SendBird.connect() after the SendBird.init() is required during the initialization process.

Local cachingSyncManager
// Initialize with the useLocalCaching set to true.
SendBird.init(APP_ID, getApplicationContext(), true, new InitResultHandler() {
   @Override
   public void onMigrationStarted() {
       Log.i("Application", "Called when there's a change in the local database.");
   }

   @Override
   public void onInitFailed(SendBirdException e) {
       Log.i("Application", "Called when initialization failed.");
   }

   @Override
   public void onInitSucceed() {
.       Log.i("Application", "Called when initialization is done.");
   }
});

SendBird.connect(userId, new SendBird.ConnectHandler() {
   @Override
   public void onConnected(User user, SendBirdException e) {
       if (user != null) {
           if (e != null) {
               // Proceed in offline mode with the data stored in the local database.
               // Later, connection will be made automatically
               // and can be notified through the ConnectionHandler.onReconnectSucceeded().

           } else {
               // Proceed in online mode.
           }
       } else {
           // No user is available.
           // Handle error.
       }
   }
});

Group channel collection

A GroupChannelCollection allows you to swiftly create a channel list without missing any channel-related updates by retrieving group channels from both the local cache and Sendbird server. This page explains how to make a channel list using the collection and serves as a migration guide.

Create a collection

SyncManager's ChannelCollection is changed to the GroupChannelCollection in local caching. The GroupChannelCollection instance can be created through the createGroupChannelCollection() method.

Create a GroupChannelListQuery instance through the createMyGroupChannelListQuery() method and query setters. This will determine the number of channels to retrieve for the channel list.

Local cachingSyncManager
GroupChannelListQuery query = GroupChannel.createMyGroupChannelListQuery();
query.setLimit(CHANNEL_LIST_LIMIT);

groupChannelCollection = new GroupChannelCollection.Builder(query)
       .setGroupChannelCollectionHandler(groupChannelCollectionHandler)
       .build();

Channel events

In local caching, the GroupChannelCollectionHandler() is used to determine how the client app would react to channel-related events. SyncManager's onChannelEvent(), which is used to handle real-time events, should be changed as shown in the code below.

The following table shows when to call each event handler.

EventCalled when

onChannelAdded

- A new group channel is created as a real-time event.
- New group channels are fetched by changelog sync.
- It replaces SyncManager's ChannelEventAction.INSERT.

onChannelUpdated

- The channel information that is included in the user's current chat view is updated as a real-time event.
- Updated channel information is fetched during changelog sync.
- It replaces SyncManager's ChannelEventAction.UPDATE and ChannelEventAction.MOVE.

onChannelDeleted

- A group channel is deleted as a real-time event.
- A channel deletion event is fetched during changelog sync.
- It replaces SyncManager's ChannelEventAction.REMOVE.

Local cachingSyncManager
groupChannelCollection.setCollectionHandler(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) {
   }
});

List channels

SyncManager's fetch() method retrieves channels from the local cache and delivers them to the ChannelCollectionHandler instance. In local caching, the GroupChannelCollection can retrieve channels through two new interfaces, hasMore() and loadMore().

By default, cached channels are listed in reverse chronological order, meaning the channel that most recently received a message appears at the top of the list. The channel order is automatically updated in the local cache when a new message arrives.

MethodsDescription

hasMore()

- Checks if there are more channels to load.
- Called whenever a user scroll reaches the bottom of the channel list.

loadMore()

- If hasMore() is true, retrieves channels from the local cache to show in the channel list.
- Called whenever a user scroll reaches the bottom of the channel list.

Local cachingSyncManager
if (groupChannelCollection.hasMore()) {
   groupChannelCollection.loadMore(new OnChannelLoadResultHandler() {
       @Override
       public void onResult(List<GroupChannel> channelList, SendBirdException e) {
          // The channel list returns as a callback.
       }
   });
}

Dispose of the collection

SyncManager's ChannelCollection has a remove() method that clears all the channels managed by the collection and stops synchronization process of the collection.

On the other hand, local caching uses the dispose() method to clear the existing channel list view. This method should be called when the user closes the channel list so the GroupChannelCollectionHandler won't make any change to the channel list.

Local cachingSyncManager
groupChannelCollection.dispose();

Message collection

A MessageCollection allows you to swiftly create a chat view without missing any message-related updates by retrieving data from both the local cache and Sendbird server. This page explains how to create a chat view using the collection and serves as a migration guide.

Create a collection

In local caching, the MessageListParams is used instead of SyncManager's MessageFilter. A MessageListParams instance will determine how to sort and order the retrieved messages. Then, specify the starting point of the message list in the chat view using the collection's builder.

Local cachingSyncManager
private void createMessageCollection() {
    // Create a MessageListParams to be used in the MessageCollection.
    MessageListParams params = new MessageListParams();
    params.setReverse(false);
    // You can add other query setters.

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

Message events

Use the setMessageCollectionHandler() to determine how the client app would react to message-related events.

A new addition to local caching is onHugeGapDetected. If more than 300 messages are missing in the local cache compared to the remote server, Sendbird Chat SDK determines that there is a huge gap. For further information, see Gap and synchronization.

The following table shows when to call each event handler.

HandlerCalled when

onMessageAdded()

- A new message is created as a real-time event.
- New messages are fetched during changelog sync.

onMessageDeleted()

- A message is deleted as a real-time event.
- Message deletion is detected during changelog sync.
- The value of the MessageListParams setter such as custom_type changes.

onMessageUpdated()

- A message is updated as a real-time event.
- Message update is detected during changelog sync.
- The sending status of a pending message changes.

onChannelUpdated()

- The channel information that is included in the user's current chat view is updated as a real-time event.
-Channel info update is detected during changelog sync.

onChannelDeleted()

- The current channel is deleted as a rea-time event.
- Channel deletion is detected during changelog sync.
- In both cases, the entire view should be disposed.

onHugeGapDetected()

- A huge gap is detected through Background sync. In this case, you need to dispose of the view and create a new MessageCollection instance.

SyncManager's onSucceededMessageEvent(),onPendingMessageEvent(), and onFailedMessageEvent() should be changed as shown in the code below.

Local cachingSyncManager
messageCollection.setMessageCollectionHandler(new MessageCollectionHandler() {
   /**
    * SyncManager's following events should be handled through the onMessagesAdded() in local caching:
    *   1. onSucceededMessageEvent() MessageEventAction.INSERT.
    *   2. onPendingMessageEvent() MessageEventAction.INSERT when sending a message.
    */
   @Override
   public void onMessagesAdded(@NonNull MessageContext context, @NonNull GroupChannel channel, @NonNull List<BaseMessage> messages) {

   }

   /**
    * SyncManager's following events should be handled through the onMessagesUpdated() in local caching:
    *   1. onSucceededMessageEvent() MessageEventAction.UPDATE.
    *   2. onPendingMessageEvent() MessageEventAction.INSERT when resending a failed message.
    *   3. onFailedMessageEvent() MessageEventAction.INSERT when sending or resending a message failed.
    */
   @Override
   public void onMessagesUpdated(@NonNull MessageContext context, @NonNull GroupChannel channel, @NonNull List<BaseMessage> messages) {
   }

   /**
    * SyncManager's following events should be handled through the onMessagesDeleted() in local caching:
    *   1. onSucceededMessageEvent() MessageEventAction.REMOVE.
    *   2. onFailedMessageEvent() MessageEventAction.REMOVE.
    */
   @Override
   public void onMessagesDeleted(@NonNull MessageContext context, @NonNull GroupChannel channel, @NonNull List<BaseMessage> messages) {

   }

   /**
    * SyncManager's onChannelUpdated() events should be handled through the onChannelUpdated() in local caching.
    */
   @Override
   public void onChannelUpdated(@NonNull GroupChannelContext context, @NonNull GroupChannel channel) {

   }

   /**
    * SyncManager's onChannelRemoved() events should be handled through the onChannelDeleted() in local caching.
    */
   @Override
   public void onChannelDeleted(@NonNull GroupChannelContext context, @NonNull String channelUrl) {

   }
    /**
    * The onHugeGapDetected() is called when the SDK detects more than 300 messages missing while connecting online.
    * The current message collection should be disposed and a new MessageCollection is created.
    */
   @Override
   public void onHugeGapDetected() {
      if (messageCollection != null) {
         messageCollection.dispose();
      }

      int firstItemPosition = ((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition();
      if (firstItemPosition >= 0) {
         final BaseMessage message = chatAdapter.getItem(position);

         messageCollection = new MessageCollection.Builder(mChannel, messageListParams)
                .setStartingPoint(message != null ? message.getCreatedAt() : messageCollection.getStartingPoint())
                .build();
      } else {
         messageCollection = new MessageCollection.Builder(mChannel, messageListParams)
                .setStartingPoint(messageCollection.getStartingPoint())
                .build();
      }
      messageCollection.setMessageCollectionHandler(messageCollectionHandler);
   }
});

SyncManager's onNewMessage() used to notify a new message that had arrived should also be changed with the code below.

Local cachingSyncManager
SendBird.addChannelHandler("MessageCollectionNewMessage", new SendBird.ChannelHandler() {
   @Override
   public void onMessageReceived(BaseChannel channel, BaseMessage message) {
       if (!this.channel.getUrl().equals(channel.getUrl())) {
           // There's no new message for this channel.
           return;
       }
       boolean isScrolledUp = ((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition() > 0;
       if (isScrolledUp) {
           showNewMessageTooltip(message);
       }
   }
});

List messages

SyncManager's fetchSucceededMessages() method retrieves messages from the local cache and delivers them to the MessageCollectionHandler instance. In local caching, the MessageCollection can retrieve and display messages through four new interfaces, hasPrevious(), hasNext(), loadPrevious(), and loadNext().

Unlike the GroupChannelCollection, pagination works in both directions for messages because messages can be shown in either chronological or reverse chronological order depending on how you set the value of startingPoint.

MethodDescription

hasPrevious()

- Checks if there are more messages to load from the previous page.
- Called whenever a user scroll hits the top of the chat view.

loadPrevious()

- If hasPrevious() is true, retrieves messages from the local cache to show in the view.
- Called whenever a user scroll hits the top of the chat view.

hasNext()

- Checks if there are more messages to load in the next page.
- Called whenever a user scroll hits the bottom of the chat view.

loadNext()

- If hasNext() is true, retrieves messages from the local cache to show in the view.
- Called whenever a user scroll hits the bottom of the chat view.

Policy

In a MessageCollection, the initialization is dictated by the MessageCollectionInitPolicy. The MessageCollectionInitPolicy determines how initialization deals with the message data retrieved from the local cache and API calls. Because we only support the InitPolicy.CACHE_AND_REPLACE_BY_API at this time, an additional implementation is required to clear the messages in the local cache before adding the messages from the remote server. Messages will first be retrieved from the cached list using the onCacheResult(). Next, the onApiResult() calls the API result list which then replaces the cached message list with messages received from the API call.

Local cachingSyncManager
// Initialize messages from the startingPoint.
messageCollection.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.
       // They might be too outdated compared to the startingPoint.
   }

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

// Next direction
if (messageCollection.hasNext()) {
   messageCollection.loadNext(new BaseChannel.GetMessagesHandler() {
       @Override
       public void onResult(List<BaseMessage> messages, SendBirdException e) {
           // The message list returns as a callback.
       }
   });
}

// Previous direction
if (messageCollection.hasPrevious()) {
   messageCollection.loadPrevious(new BaseChannel.GetMessagesHandler() {
       @Override
       public void onResult(List<BaseMessage> messages, SendBirdException e) {
           // The message list returns as a callback.
       }
   });
}

SyncManager's resetViewpointTimestamp is used to reset the current message collection to the specified time. To reset a viewpoint timestamp in local caching, it is suggested that you dispose the current message collection and create a new collection.

Local cachingSyncManager
messageCollection.dispose();
messageCollection = new MessageCollection.Builder(channel, messageListParams)
       .setStartingPoint(TARGET_TIMESTAMP)
       .build();

Send a message

In local caching, the result of sending a message is handled internally through the MessageCollectionHandler. First, pending messages are delivered to local caching's MessageCollectionHandler.onMessagesAdded(). Whether the message succeeds or fails in sending, the result will be delivered to the MessageCollectionHandler.onMessagesUpdated(). Thus, the process of using a onSent() callback from the sendUserMessage() and the sendFileMessage() to manually input the send result in SyncManager is no longer needed.

Note: Don't add the pending, succeeded or failed message objects of the sendMessage() callback to your message list data source. This can cause duplicate messages in the chat view.

Local cachingSyncManager
// User Message
channel.sendUserMessage(userMessageParams, null);

// File Message
channel.sendFileMessage(fileMessageParams, (BaseChannel.SendFileMessageHandler) null);

Resend a failed message

In local caching, the result of resending a message is handled internally through the MessageCollectionHandler. First, pending messages are delivered to local caching's MessageCollectionHandler.onMessagesUpdated(). Whether the message has succeeded or failed in sending, the result will be delivered to the MessageCollectionHandler.onMessagesUpdated(). Thus, the process of using a onSent() callback from the resendMessage() to manually input the send result in SyncManager is no longer needed.

Note: Don't add the pending, succeeded or failed message objects of the resendMessage() callback to your message list data source. This can cause duplicate messages in the chat view.

Local cachingSyncManager
channel.resendMessage(userMessage, null);

List<BaseMessage> pendingMessageList = messageCollection.getPendingMessages();
List<BaseMessage> failedMessageList = messageCollection.getFailedMessages();

Update a message

SyncManager uses the onUpdated() callback to manually update message in collection after updating the same message in a channel. In local caching, however, updating messages is handled internally and is delivered to the MessageCollectionHandler.onMessagesUpdated().

Local cachingSyncManager
channel.updateUserMessage(message.getMessageId(), new UserMessageParams(EDITED_MESSAGE), null);

Delete a message

There are two cases in deleting a message:

Deleting a sent message.

In SyncManager, the process of delivering the result of message deletion through the deleteMessage() was required. However, this process is not needed in local caching.

Deleting a failed message.

The process is the same for both SyncManager and local caching where the failed message object is deleted explicitly from the local cache. In SyncManager, failed messages are deleted through the deleteMessage(). The same can be done through the removeFailedMessages() in local caching.

Local cachingSyncManager
// 1. Deleting a succeeded message. Delete from the channel object. The result will be delivered to 
//the MessageCollectionHandler.onMessagesDeleted().
channel.deleteMessage(message, null);

// 2-1. Deleting a failed message. Delete from the collection only.
messageCollection.removeFailedMessages(Collections.singletonList(message), new RemoveFailedMessagesHandler() {
   @Override
   public void onResult(List<String> requestIds, SendBirdException e) {

   }
});

// 2-2. Deleting all failed messages. Delete from the collection only
messageCollection.removeAllFailedMessages(new RemoveAllFailedMessagesHandler() {
   @Override
   public void onResult(SendBirdException e) {
       // All failed messages are deleted.
   }
});

Dispose of the collection

SyncManager's MessageCollection has a remove() method that clears all the messages managed by the collection and stops synchronization processes in the collection instance.

On the other hand, local caching uses the dispose() method to clear the existing chat view. You should call this method when the current user leaves the channel or when a new collection has to be created due to a huge gap detected by the onHugeGapDetected().

Local cachingSyncManager
messageCollection.dispose();

Other methods

The following are new cache-related interfaces that come with local caching. Local caching's clearCachedData() replaces the functionality of SyncManager's clearCache(), which is used to delete the entire data file. In addition, the clearCachedMessages() and the getCachedDataSize() is available in local caching. The following table describes each method and when it should be called in regards to the SendBird.init().

MethodDescription

clearCachedData()

- Deletes the entire data file.
- It must be called before the SendBird.init().

clearCachedMessages()

- Clears cached messages in a specific channel.
- It must be called after the SendBird.init().

getCachedDataSize()

- Checks for the current cached data size.
- It can be called before or after the SendBird.init() is called.

Local cachingSyncManager
SendBird.clearCachedData(Context, CompletionHandler);
SendBird.clearCachedMessages(CHANNEL_URL, CompletionHandler);
long cacheDataSize = SendBird.getCachedDataSize(Context);

In addition, SyncManager's messageCollection.getMessageCount() and messageCollection.contains(message) are replaced with messageCollection.getSucceededMessages() in local caching. It is used to count the number of succeeded messages in the collection.

Local cachingSyncManager
// The messageCollection provides a getter for the current message list in the message collection.
List<BaseMessage> succeededMessageList = messageCollection.getSucceededMessages();
int messageCount = succeededMessageList.size();
boolean contains = succeededMessageList.contains(message);