Synchronization

Synchronization happens automatically in near-real-time (we don’t use a polling interval). Some methods exist to request historical messages to be synced to the device if they haven’t been synced already.

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

If you lose network connectivity you can continue to create conversations, send messages, change participants, etc… while offline. Almost all of our SDK methods operate on locally synced data and these events are queued when offline. Once a connection is re-established they will be automatically sent with no additional steps or configuration needed.

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.

LayerKit leverages the NSNotification broadcast mechanism to notify your application when changes occur. Your application should observe LYRClientObjectsDidChangeNotification in order to receive notifications.

// Adds the notification observer
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(didReceiveLayerObjectsDidChangeNotification:)
                                             name:LYRClientObjectsDidChangeNotification object:layerClient];

Upon receipt of a LYRClientObjectsDidChangeNotification, your application can obtain a reference to the changes it contains by retrieving the LYRClientObjectChangesUserInfoKey key from the userInfo dictionary of the notification object. This key references an array where each element is a dictionary that describes a specific change to a conversation or message object.

// Returns an array of the change dictionaries

 NSArray *changes = [notification.userInfo objectForKey:LYRClientObjectChangesUserInfoKey];

Change notifications occur for both LYRMessage and LYRConversation objects. Your application can retrieve the type of object upon which a change has occurred by retrieving the object property from the LYRObjectChange object.

for (LYRObjectChange *change in changes) {
    id changeObject = change.object;
    if ([changeObject isKindOfClass:[LYRConversation class]]) {
        // Object is a conversation
    }

    if ([changeObject isKindOfClass:[LYRMessage class]]) {
        // Object is a message
    }
}

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 retrieve the type property from the LYRObjectChange object.

LYRObjectChangeType updateKey = change.type;
switch (changeKey) {
    case LYRObjectChangeTypeCreate:
        //Object was created
        break;
    case LYRObjectChangeTypeUpdate:
        // Object was updated
        break;
    case LYRObjectChangeTypeDelete:
        // Object was deleted
        break;
    default:
        break;
}

The above code can be combined:

- (void)didReceiveLayerObjectsDidChangeNotification:(NSNotification *)notification;
{
    NSArray *changes = [notification.userInfo objectForKey:LYRClientObjectChangesUserInfoKey];
    for (LYRObjectChange *change in changes) {
        id changeObject = change.object;
        LYRObjectChangeType updateKey = change.type;
        if ([changeObject isKindOfClass:[LYRConversation class]]) {
            // Object is a conversation
            LYRConversation *message = changeObject;

            switch (updateKey) {
                case LYRObjectChangeTypeCreate:
                    //
                    break;
                case LYRObjectChangeTypeUpdate:
                    //
                    break;
                case LYRObjectChangeTypeDelete:
                    //
                    break;
                default:
                    break;
            }
        } else {
            // Object is a message
            LYRMessage *message = changeObject;

            switch (updateKey) {
                case LYRObjectChangeTypeCreate:
                    //
                    break;
                case LYRObjectChangeTypeUpdate:
                    //
                    break;
                case LYRObjectChangeTypeDelete:
                    //
                    break;
                default:
                    break;
            }
        }
    }
}

Historic synchronization

Historic sync allows the client to specify which existing message to automatically retrieve after it has authenticated and connected. 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, the Layer SDK will sync all messages starting with the earliest unread message in each conversation.

The historic sync policy is set when you create a Layer client by passing in LYRClientOptions in the initializer.

LYRClientOptions *clientOptions = [LYRClientOptions new];
clientOptions.synchronizationPolicy = LYRClientSynchronizationPolicyUnreadOnly; // Default policy

[LYRClient clientWithAppID:@"APP_ID" delegate:self options:clientOptions];

We currently support three sync policies:

  • LYRClientSynchronizationPolicyCompleteHistory: This will sync all messages for all conversations. Depending on how much history is in the conversations the authenticating user is part of, this may consume a lot of bandwidth and significantly affect app performance during the sync.
  • LYRClientSynchronizationPolicyUnreadOnly: This will sync all messages as far back as the first unread message in each conversation. If all messages have been marked as read, this syncs the last (most recent) message in each conversation. This is the default policy.
  • LYRClientSynchronizationPolicyPartialHistory: This will sync a specified number of messages, starting from the most recent. The partialHistoryMessageCount property determines how many messages to sync:
// Sync the 20 most recent messages in each conversation
LYRClientOptions *clientOptions = [LYRClientOptions new];
clientOptions.synchronizationPolicy = LYRClientSynchronizationPolicyPartialHistory;
clientOptions.partialHistoryMessageCount = 20;

[LYRClient clientWithAppID:@"APP_ID" delegate:self options:clientOptions];

If Layer is configured with LYRClientSynchronizationPolicyUnreadOnly or LYRClientSynchronizationPolicyPartialHistory (that is, Layer doesn’t automatically sync all available messages), you can then sync more history as needed using a few properties and methods on LYRConversation:

  • totalNumberOfMessages: An NSUInteger representing the total number of messages in the conversation, including those that have not yet been synced
  • totalNumberOfUnreadMessages: An NSUInteger representing the total number of unread messages, including those that have not yet been synced
  • -(BOOL)synchronizeMoreMessages:(NSUInteger)minimumNumberOfMessages error:(NSError *)error: Invoke this method to sync at least minimumNumberOfMessages more messages than what is already synced, starting from the most recent and counting backwards
  • -(BOOL)synchronizeAllMessages:(LYRMessageSyncOptions)messageSyncOption error:(NSError *)error: Invoke this method to sync all past messages in this conversation. You can also sync messages up to the first unread messages by specifying LYRMessageSyncToFirstUnread as the messageSyncOption. This option is useful in combination with LYRClientSynchronizationPolicyUnreadOnly.

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 object, LayerKit provides a convenience method that notifies you when that conversation/message has been synchronized if it hasn’t already called waitForCreationOfObjectWithIdentifier:

NSURL *messageURL = [NSURL URLWithString:@"layer:///messages/2f002ba4-2a08-4019-9fa6-0fe30e2ac3f7"];
// Retrieve LYRMessage from Message URL
LYRQuery *query = [LYRQuery queryWithQueryableClass:[LYRMessage class]];
query.predicate = [LYRPredicate predicateWithProperty:@"identifier" predicateOperator:LYRPredicateOperatorIsEqualTo value:messageURL];

NSError *error = nil;
NSOrderedSet *messages = [self.layerClient executeQuery:query error:&error];
if (error) {
    // query failed
}
__block LYRMessage *message = messages.firstObject;
if (!message) {
    // The message hasn't been synchronized
    [self.layerClient waitForCreationOfObjectWithIdentifier:messageURL timeout:3.0f completion:^(id  _Nullable object, NSError * _Nullable error) {
        if (object) {
            message = (LYRMessage*)object;
            // proceed with object
        } else {
            // object wasn't created in timeout with error
        }
    }];
} else {
    // proceed with object
}

Client delegate

The LYRClientDelegate protocol also declares synchronization methods which alert your application when objects have changed or when an operation has failed.

- (void)layerClient:(LYRClient *)client objectsDidChange:(NSArray *)changes;
{
    NSLog(@"Layer Client did finish synchronization");
}

- (void)layerClient:(LYRClient *)client didFailOperationWithError:(NSError *)error
{
    NSLog(@"Layer Client did fail Synchronization with error:%@", 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