Synchronization

Synchronization happens automatically in near-real-time (we don’t use a polling interval). Some methods exist to force a manual sync of historical events.

When your app is opened, the client SDKs will pull new data since the last sync from the server. Subsequent updates are pushed from the server to the client.

Our sync implementation leads to a few important considerations:

  • Sync does not happen instantaneously. If you are expecting a Layer object (conversation, message, announcement, etc) to exist (for example, if you created it via the Server API), but it does not, it may be because the object has not synced yet. There are client SDK methods that accept an ID and let you know when the object has synced.
  • When syncing, Layer objects may be received out-of-order. However, we will take care of maintaining the correct ordering of objects as they are received. Depending on how you implement your UI, this may mean that, for example, you see messages appear between other on-screen messages.
  • We only sync objects that are relevant to the currently authenticated user. This means we won’t sync conversations the current user isn’t part of, or announcements that weren’t sent to the current user.
  • Content is synced in the background. Unexpected behavior may arise from race conditions in performing operations before all content has synced.
  • Almost all of our SDK methods, including sending messages, querying, and marking messages as read, operate on locally synced data. This can lead to unexpected behavior, including incorrect badge counts, if the client was partially synchronized or hasn’t finished synchronizing. By default, we don’t sync everything — see Historic sync (below) for details.

Common mistake

Make sure you’re aware of sync progress (see Notifications below), and that most methods only operate on locally synced data. For example, if you have a conversation with 10 unread messages, but only one of those has synced so far, marking all messages as read will only affect that one message (and decrement the badge count by 1).

Offline Operations

Notifications

Layer provides a flexible notification system for notifying your app when changes have occurred in response to data being synced. The system is designed to be general purpose and alerts your application to the creation, update, or deletion of an object. This is an advanced feature and is intended for use in applications that require granular detail into everything occurring within Layer.

Layer leverages listeners to notify your application when changes happen. Your application should register as a LayerChangeEventListener to receive change notifications. After synchronization completes, all registered LayerChangeEventListeners will be notified.

public class MyApplication extends Application implements LayerChangeEventListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        LayerClient client = LayerClient.newInstance(this, "layer:///apps/staging/cf10048c-d9ab-11e5-b6a9-c01d00006542");
        client.registerEventListener(this);

    }
}

Upon receipt of a call to onChangeEvent(), your application can obtain a reference to all changes by calling getChange() on the event object. This will return an array value where each element describes a specific change to a conversation or message object:

public void onChangeEvent(LayerChangeEvent event) {
    List<LayerChange> changes = event.getChanges();
}

Change notifications occur for both Message and Conversation objects. Your application can retrieve the type of object upon which a change has occurred by calling getObjectType() on the change object:

switch (change.getObjectType()) {
     case CONVERSATION:
     // Object is a conversation
     break;

     case MESSAGE:
     // Object is a message
     break;
}
break;

Change notifications will alert your application to object creation, update and deletion events. In order to acquire the specific type of change, your application can call getChangetype() on nthe change object:

switch (change.getChangeType()) {
    case INSERT:
    // Object was created
    break;

    case UPDATE:
    // Object was updated
    break;

    case DELETE:
    // Object was deleted
    break;
}
break;

Your application can get the actual updated object by calling change.getObject():

Object changeObject = change.getObject();

Historic synchronization

With historic synchronization, the client can specify which messages should be retrieved after authentication. Tweaking these settings allows you to deliver a better onboarding experience to the messaging component of your app by improving performance, reducing the time it takes to begin displaying content, and minimizing data consumption. By default, Layer will sync messages starting with the earliest unread message in the conversation. You can change this by setting an Option on LayerClient. There are three options that can be passed into the options object:

  • FROM_EARLIEST_UNREAD_MESSAGE: This option will retrieve all messages from the earliest unread messages. This is the default behavior.
  • ALL_MESSAGES: This option will sync all messages from every conversation. This might significantly affect bandwidth and performance.
  • FROM_LAST_MESSAGE: This will sync only the last message of each conversation.
final LayerClient.Options options = new LayerClient.Options();
options.historicSyncPolicy(LayerClient.Options.HistoricSyncPolicy.ALL_MESSAGES);

If FROM_EARLIEST_UNREAD_MESSAGE or FROM_LAST_MESSAGE is selected, then you can check a conversation’s message count and choose to retrieve more of the message history from the servers:

  • getTotalMessageCount(): This method will return the total number of messages in a conversation, including all historic messages that have yet to be synced.
  • getTotalUnreadMessageCount(): This method will return the total number of unread messages in a conversation including all unread historic messages.
  • syncMoreHistoricMessages(int suggestedNumberOfMessages): This method will sync at least the specified number of historic messages in a conversation.
  • syncFromEarliestUnreadMessage: Synchronizes message history starting from the earliest unread Message up to the last message.
  • syncAllHistoricMessages(): This method will sync all messages in the conversation.
  • getHistoricSyncStatus(): This method will return one of values from HistoricMessageStatus. Based on the returned value, you can determine the historic message status for the conversation. The states are as follows:
    • NO_MORE_AVAILABLE: This state is returned if the all the messages have been downloaded from the server.
    • MORE_AVAILABLE: This state is returned if there are messages that could be synced from the server.
    • SYNC_PENDING: This state is returned when there is a pending request to sync historic messages from the server.

Listening for a specific conversation or message

A common use case is to create a conversation/message via the Server API, and then access it in the SDK. A common mistake developers make is to try to query for the conversation/message before it’s been synchronized to the device.

To assist in receiving the content, the SDK provides a convenience method called waitForContent, that notifies you when that conversation/message has been synchronized.

layerClient.waitForContent(Uri.parse("layer:///conversations/7b3e0109-c411-434e-965d-f07b62705bc1"),
    new LayerClient.ContentAvailableCallback() {
        @Override
        public void onContentAvailable(LayerClient client, Queryable object) {
            // Content has been synchronized
        }

        @Override
        public void onContentFailed(LayerClient client, Uri objectId, Exception exception) {
            // Failed to fetch content
        }
    }
);

Synchronization listener

The SDK also provides a synchronization listener that alerts your application when a synchronization is about to begin, its progress as it runs, and when synchronization has successfully completed. The callback functions onBeforeSync, onSyncProgress and onAfterSync will execute during each stage of the process. You will be notified of each type of sync: SyncType.HISTORIC will be used the first time a user authenticates and their conversation history is downloaded (based on the option you set), and each subsequent sync is considered a SyncType.NORMAL.

If there are any problems during the sync process (for example, loss of network connectivity), onSyncError will be called with the appropriate error. Your application should register as a LayerSyncListener to receive these call backs:

public class MyApplication extends Application implements LayerSyncListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        LayerClient client = LayerClient.newInstance(this, "layer:///apps/staging/cf10048c-d9ab-11e5-b6a9-c01d00006542");
        client.registerSyncListener(this);

    }

    void onBeforeSync(LayerClient client, SyncType syncType) {
        // LayerClient is starting synchronization
        Log.v(TAG, "onBeforeSync");
    }
    void onSyncProgress(LayerClient client, SyncType syncType, int progress) {
        // LayerClient synchronization progress
        Log.v(TAG "onSyncProgress: " + progress/100.0f);
    }
    void onAfterSync(LayerClient client, SyncType syncType) {
        // LayerClient has finished synchronization
    }
    void onSyncError(LayerClient client, List<LayerException> exceptions) {
        // Sync has thrown an error
    }
}

Best practice

For conversations with more than 5 participants, you should disable delivery and read receipts to speed up syncing and improve overall performance. Click here to learn more.

Metadata Push notifications