Layer to SendBird Migration
Since Layer announced it will shut down its chat service on October 30, we’ve created a set of resources to make it easy for businesses and developers to migrate their chat provider without interruptions in service.
Our solutions engineering team has already developed a sync server that will migrate your historical chat data, while synchronizing your realtime chat data with SendBird’s servers. This sync server allows you to migrate your chat without service interruptions or data loss.
In this guide, we offer a guide for implementing the iOS client and compare the key differences between Layer and SendBird’s iOS client. We’ll use Layer’s Atlas and Atlas Messenger throughout and share comparable code in SendBird’s sample.
Although this guide intends to make the client implementation of SendBird as easy as possible for developers familiar with Layer Atlas and Layer’s SDK, the two products differ in many ways, making a perfect 1:1 match difficult. This guide highlights differences between the client-side implementation and suggests workarounds, where possible. Links throughout this guide refer you to SendBird’s iOS documentation and reference material.
Key differences between Layer and SendBird iOS client implementation
The following section highlights key differences between the iOS client implementation of SendBird and Layer. This article will compare code directly later, but for now, emphasizes qualitative differences in:
- Layer’s Conversation and SendBird’s Group Channel
- The architecture of messages for Layer and SendBird
- Receiving messages in realtime
- Read receipts
When a client (the user of your app) uses Layer, it needs a nonce for identification. Then the identity provider creates an identity token using the nonce and user credentials.
With SendBird, each user has an access token, generated by an authentication server using SendBird’s Platform API. This guide assumes you have a user authentication server that can create a user via Platform API because it is the only way to generate an access token for the user.
After creating a user with an access token, the user authentication server must save the access token and the user in the server's database. When the client signs in with a user ID and password, the user authentication server returns the user’s access token. The client can connect to the SendBird servers with both the user ID and the access token.
Layer Conversation vs. SendBird’s Group Channel
Layer’s Conversation is most like SendBird’s Group Channel, so previous customers of Layer will want to understand the differences and similarities between Conversation and Group Channel – how to create a Group Channel, query and leave it. There are, of course, differences between the two. But for our purposes, think of Conversation and Group Channel as different terminology for a similar purpose.
The Layer Conversation object is created when two or more users send the first message to each other. It has a unique ID, a name, users who message each other, read receipts and typing indicators.
Create a Conversation || Create a Group Channel
You can create Group Channels in SendBird similar to how you would create a Conversation in Layer. The main difference is that SendBird uses a callback method to return the new channel, so it requires a connection to SendBird.
Since Layer uses a local DB and creates the Conversation in the local DB first, it synchronizes the Conversation with the remote server after the connection is established. As a result, Layer does not use a callback.
Additional channel type in SendBird
SendBird also has an Open Channel with different properties than the Group Channel. The Open Channel is ideal for a large number of participants or when you only need a small and fixed number of channels. You can read more at the documentation for Open Channels.
Layer and SendBird use a different structure of classes to represent message objects.
LYRMessage consists of several LYRMessagePart and each LYRMessagePart has a MIME type for content. So a single message, for example, may consist of text and an image. In this case, LYRMessage comprises two LYRMessagePart. For the text message, Layer uses MIMETypeTextPlain or text/plain, and MIMETypeImagePNG or image/png.
On the other hand, SendBird uses two different classes for text and files: SBDUserMessage and SBDFileMessage. You can specify a CUSTOM_TYPE for each to further subclassify, send structured data like font size or type, or another customer JSON object for a text message. As we’ll see below, this also allows you to send location data with a user message.
To send a file, SendBird uses SBDFileMessage to represent a file message. SendBird uploads a file to the server, receives a URL for the file and sends an SBDFileMessage object with the URL.
There are significant differences between Layer and SendBird’s read receipts. Layer marks messages read for each message. SendBird, on the other hand, marks messages read for a single channel. When markAsRead of the SBDGroupChannel object is called, SendBird indicates that the user has read all messages in the channel.
Layer also has 5 separate statuses: Invalid, Pending, Sent, Delivered and Read.
SendBird returns only two statuses: Unread and Read.
We can guarantee that a message is sent and delivered if the sendUserMessageWithParams:completionHandler: or sendFileMessageWithParams:progressHandler:completionHandler: return an error as nil via their completion handler callback. If the callback returns an error, you can implement something to resend the message.
Receiving messages in realtime
When Layer receives a new message, LYRQueryController instance calls the queryControllerDidChangeContent: method of the LYRQueryControllerDelegate delegate. The LYRQueryController instance has the new message and the client can display the message.
SendBird uses a delegate to receive many types of events (see the full list at SBDChannelDelegate). To receive a realtime message, the client has to implement the channel:didReceiveMessage: method of SBDChannelDelegate delegate. The delegate method receives every message in every channel the current user has joined. It means that the client has to filter the message by the channel.
Install the SendBird SDK for iOS
SendBird offers the SDK on CocoaPods (https://cocoapods.org/) and Carthage (https://github.com/Carthage/Carthage). You can also use the Quick Start guide in SendBird’s iOS documentation.
You must initialize SendBird and Layer before using the SDKs.
Layer's App ID is a URL format. SendBird’s App ID is a unique string.
Layer creates the LYRClient instance with the app ID.
From Atlas Messenger:
SendBird initializes itself with the app ID via initWithApplicationId: and it generates a singleton instance. This means that _layerClient in the above is similar to the singleton instance inside the SendBird SDK. The application:didFinishLaunchingWithOptions: is the best place to put the initialization.
Recall the difference between Layer and SendBird during authentication described above.
Layer’s client needs a nonce for authentication. The identity provider generates an
identity token with the user’s credentials and the nonce. The client connects to Layer with the identity token.
From Atlas Messenger:
SendBird does not use a nonce or an identity token. Instead, each user has an access token, which is similar to a password that the authentication server generates from SendBird’s Platform API (Create User).
This guide assumes that you have a user authentication server that can create a user on SendBird via Platform API (Create User). This API is the only way to generate an access token for the user. After creating a user with an access token, the user authentication server must save the access token with the user ID in the server's database.
When the client signs in with user ID and password, the user authentication server returns the user’s access token. The client can connect to SendBird and begin messaging with the user ID and the access token.
Layer Conversation vs. SendBird Group Channel
Recall differences and similarities between Layer’s Conversation and SendBird’s Group Channel described above. This section guides you through creating, querying and leaving both Layer’s Conversation and SendBird’s Group Channel.
Creating a Conversation || Group Channel
Create a Conversation in Layer
To create a new Conversation in Layer, you need the participants. The newConversationWithParticipants:participantIdentifiers:error: method returns a new Conversation. This method doesn't have a callback because Layer uses a local DB and creates the Conversation in the local DB first. It synchronizes the Conversation with the remote server after the connection is established. Layer creates Conversations with several options. One option in LYRConversationOptions is distinctByParticipants. The default value is YES.
Create a Group Channel in SendBird
SendBird also requires the user_id of participants to create a group channel. Unlike Layer, SendBird uses a callback method. SendBird returns the new channel via the callback, so it requires a connection to successfully create a channel.
Querying a Conversation || Group Channel
Query a Conversation in Layer
Atlas, or an app that uses LayerKit, uses LYRQueryController to query objects in Layer. To query Conversations, the client needs to create a LYRQuery instance for the LYRConversation class. This creates the LYRQueryController instance at the same time as the LYRQuery instance. To see the result of the query, the LYRQueryController updates a set of objects. To notify the query is finished, the LYRQueryController uses a delegate, LYRQueryControllerDelegate. The queryController:didChangeObject:atIndexPath:forChangeType:newIndexPath: notifies the client that there are updates in the set of objects in LYRQueryController.
Query a Group Channel in SendBird
SendBird also has a class for querying objects. To query channels, the client needs to create a SBDGroupChannelListQuery instance with the createMyGroupChannelListQuery class method of SBDGroupChannel.
The SBDGroupChannelListQuery has loadNextPageWithCompletionHandler: to request the next page of channels. Since the channels are returned via the completionHandler(), the query instance does not contain any set of the channels. Instead, the query instance has a flag to check whether or not there is a next page. To load the channel’s next page, use the same instance of the query and call loadNextPageWithCompletionHandler: again.
If you want to learn how to receive any channel events, please read the documentation on advanced implementations of the group channel.
Leaving a Conversation || Group Channel
Leaving a Conversation in Layer
Layer has the leave: method in the LYRConversation instance.
From Atlas Messenger:
Leaving a Group Channel in SendBird
SendBird has the same feature in SBDGroupChannel. If a user leaves the channel, the user does not receive messages from the channel.
Sending different message types
Refer to the differences between between Layer and SendBird’s message architecture described above.
Sending a text Message
Text messages in Layer
Layer needs a conversation instance and a message instance to send a message. LYRMessage consists of several parts, or LYRMessagePart. Each LYRMessagePart has a MIME type for its content. For text messages, Layer uses ATLMIMETypeTextPlain for text/plain MIME type.
Text messages in SendBird
SendBird's SBDUserMessage represents a text message. The sendUserMessageWithParams:completionHandler: method returns a temporary message and the completion handler of the method returns the result of the message transmission. You can use the temporary message to show the message that is in the process of being sent. The temporary message has a request ID. The method’s callback returns a message with the same request ID as the temporary message. If these request IDs match, the client can determine that the temporary message was sent. SendBird recommends that the client replace temporary messages with the returned messages in the data source used by the table view or the collection view.
Sending a location
While Layer creates a location object that becomes a part of LYRMessage, SendBird uses the data property of a user message to send location. Refer to the architectural differences in messages above.
Sending a location in Layer
Atlas sends a location message containing latitude-longitude coordinate. It is not a feature of the Layer SDK, but it is a useful feature for messaging. To represent location, Atlas generates a message part that contains JSON data for latitude and longitude.
The above method returns an ATLLocationMediaAttachment object of Atlas. Then, the object becomes a part of LYRMessage and can be sent.
Sending a location in SendBird
SendBird uses a user message for location. Every message type in SendBird has a data property. It is an NSString type and it can hold any string. So, the client stringifies JSON data containing the coordinates and sets it to the data property.
When the client displays the message as a location view, the customType of the message is the crucial property because the text message and the location message are the same class type, SBDUserMessage.
Sending a file
Sending a file in Layer
The LYRMessagePart can hold binary data or stream data. Layer uses it to send files. Atlas creates an ATLAssetMediaAttachment object that contains the binary data.
Then, Layer sends the LYRMessage object that has the ATLAssetMediaAttachment object.
Sending a file in SendBird
SendBird has a SBDFileMessage class to represent a file message. To send a file message, the client creates a SBDFileMessageParams object that contains both the binary file data and some additional information. The sendFileMessageWithParams:progressHandler:completionHandler: method sends the file message with the SBDFileMessageParams object. SendBird uploads the file to the server, receives a URL for the file and sends a SBDFileMessage object with the corresponding URL.
If you’ve activated the premium feature, auto-thumbnail generation, then the server generates thumbnails for the image or video and puts the URLs for the thumbnails in the SBDFileMessage object. The SBDFileMessage only has the file URLs for the original and the thumbnails. The client must use the URLs to show the file or the thumbnail.
Receiving messages in realtime
Receiving realtime messages in Layer
When Layer receives a new message, the LYRQueryController instance calls the queryControllerDidChangeContent: method of the LYRQueryControllerDelegate delegate. Once the LYRQueryController instance has the new message, the client can display the message.
Receiving realtime messages in SendBird
SendBird uses a delegate to receive many types of events. You can see a list of all the events in the reference to SBDChannelDelegate. To receive a realtime message, the client must implement the channel:didReceiveMessage: method of SBDChannelDelegate delegate. Since the delegate method receives every message in every channel that the current user has joined, the client must filter the message by channel.
Read receipts have very different implementations in Layer and SendBird. Refer to the differences in the section above for an overview.
Read receipts in Layer
The LYRMessage object in Layer has a dictionary property for the read receipt of itself. It uses a user ID for the key and a status as the value.
Atlas uses the recipientStatusByUserID property to display a recipient’s status. Atlas calls conversationViewController:attributedStringForDisplayOfRecipientStatus: of ATLConversationViewControllerDataSource. The view controller implements the method and shows the recipient’s status for each message. Layer has 5 types of the recipient status in LYRRecipientStatus.
From Atlas Messenger:
Layer can mark as read for each message via markAsRead: method of LYRMessage object
From Atlas Messenger:
Read receipts in SendBird
SendBird has only two statuses: read and unread.
Unlike Layer, SendBird does not have “Pending,” “Sent,” and “Delivered” statuses. SendBird can guarantee that the message is sent and delivered if the sendUserMessageWithParams:completionHandler: or sendFileMessageWithParams:progressHandler:completionHandler: return nil via their completion handler callback. If the callback returns an error, implement something to resend the message.
There is a key difference between SendBird and Layer for marking messages as read.
Layer marks as read for each message.
Typing indicators in Layer
Layer indicates who is typing in the conversation. The client calls the sendTypingIndicator: method of the LYRConversation object when the user is typing a message.
The client receives the typing event via NSNotificationCenter.
Typing indicators in SendBird
SendBird has a similar method for notifying when a user is typing to the group channel. The SBDGroupChannel object has two methods for it. When the user is typing, the client should call the startTyping method. When the user finishes typing, the client should call the endTyping.
To receive the typing event, the client has to implement SBDChannelDelegate and add the delegate to SendBird. Then, the delegate method, channelDidUpdateTypingStatus:, receives the typing event. The method returns a channel object that has the latest typing status.
This concludes our guide to implementing the iOS client in SendBird after migrating from Layer. While this guide highlights many of the key qualitative differences and compares code side-by-side, migrating from one service to another is never an easy undertaking. If you need any technical assistance migrating your client implementation to SendBird, contact our technical support.
Software Engineer - Applications (iOS)