Skygear Chat
Skygear Chat Basic (iOS)
Skygear Chat is a collection of APIs to help you build Chat apps much easier.
Skygear Chat is built on top of the cloud database and the PubSub module. If you want to know more about how the underlying works, check out the guide for cloud database and PubSub.
Enabling Chat
To start using Skygear Chat feature, make sure you have already enabled Chat in the Plug-ins tab in your [Skygear Portal] (https://portal.skygear.io). Please refer to Quick Start for the instructions.
Users
Most real-time chat apps require a user login system and you can do it with the Skygear user authentication module. It offers various sign up methods - sign up with email, sign up with username and sign up with Facebook and Google account.
Check out the guide for User Authentication Basics and User Profile Best Practices.
Conversations
In order to send messages, you need to create a conversation first. You can consider conversations as chatrooms or channels in your application.
There are two types of conversations in Skygear Chat:
- Direct Conversation, a chatting group between one and another user
- Group Conversation, a chatting group among 2 or more users
Creating direct conversations
You can use createDirectConversation
to create a conversation with another user. Please specify the user ID as userID
.
SKYContainer.default().chatExtension?.createDirectConversation(userID: userBen,
title: "Chat with Ben",
metadata: nil,
completion: { (conversation, error) in
if error != nil {
print ("Create direct conversation failed." +
"Error:\(error.localizedDescription)")
return
}
print ("Created direct conversation")
})
The above example creates a direct chat between the current user and userBen
.
You can also set up an optional parameter, metadata
, for customization.
Creating group conversations
Besides direct chats, you can also create a group conversation with 2 or more people.
Instead of a single userID
parameter, createConversation
accepts a list of users called participantIDs
. A new conversation will be created with the given participant IDs.
SKYContainer.default().chatExtension?.createConversation(
participantIDs: [userBen, userCharles, userDavid, userEllen],
title: "Random chat group",
metadata: nil,
completion: { (conversation, error) in
if error != nil {
print ("Create conversation failed." +
"Error:\(error.localizedDescription)")
return
}
print ("Created Conversation")
})
Creating group chat by distinct participants (distinctByParticipants
)
By default, createConversation
allows you to create multiple conversations with identical participants. However, depending on application design, it may not be a desired behavior. Therefore, Skygear Chat provides distinctByParticipants
option, which enables distinctive participants in every conversation.
The default value of distinctByParticipants
is false
. If it is set to be true
, then the participants cannot be identical to any existing conversation. Otherwise, an error will be returned.
SKYContainer.default().chatExtension?.createConversation(
participantIDs: [userBen, userCharles, userDavid],
title: "Our chat group",
metadata: nil,
adminIDs: nil,
distinctByParticipants: true,
completion: { (conversation, error) in
if let err = error as NSError? {
if let conversationId = err.userInfo["conversation_id"] as? String {
print("Conversation already exists " +
conversationId)
} else {
print ("Create conversation failed. " +
"Error:\(error.localizedDescription)")
}
}
print ("Created conversation")
})
If the conversation already exists, then completion
callback returns an error object. You can retrieve the original conversation ID via err.userInfo["conversation_id"]
.
Note: distinctByParticipants
will be false
automatically after participant list is being altered.
There are a few more attributes you can specify when you create a conversation with createConversation
.
- title: That's the title of a conversation. It can be omitted.
- meta: It is for application specific purpose. Say in your app you have different type of conversations, you can specific it here.
Setting admins
All users in participantIDs
will be administrators unless adminIDs
is specified in createConversation
.
SKYContainer.default().chatExtension?.createConversation(
participantIDs: [userBen, userCharles, userDavid],
title: "Ben's world",
adminIDs: [userBen],
distinctByParticipants: false,
completion: { (conversation, error) in
if error != nil {
print("Unable to admin. " +
"Error:\(error.localizedDescription)")
return
}
print("Added as admin")
})
In the above example, userBen
will be the only administrator.
Fetching existing conversations
In SKYKitChat
, SKYConversation
represents a conversation, it contains information of a conversation that is shared among all participants, such as participantIds
and adminIds
.
There are two methods to fetch existing conversations:
Fetch all conversations which involves the current user. The parameter fetchLastMessage
can be used for fetching the last message and last read message in the conversation.
SKYContainer.default().chatExtension?.fetchConversations(
fetchLastMessage: false,
completion: { (conversations, error) in
if let err = error {
print ("Error when fetching conversations. " +
"Error:\(err.localizedDescription)")
return
}
if let fetchedConversations = conversations {
print("Fetched \(fetchedConversations.count) conversations.")
}
})
Please refer to the documentation for more options here.
Leaving conversations
To leave a conversation, you can call leave(conversationID:completion:)
on the chat extension.
SKYContainer.default().chatExtension?.leave(
conversationID: conversationID!) { (error) in
if error != nil {
print("Unable to Leave Conversation. " +
"Error:\(error.localizedDescription)")
return
}
print("Left Conversation")
)
Getting Participants
Once you get conversation objects via fetchConversationWithConversationID
or fetchConversationsWithCompletion
, you can get the IDs of participants from participantIds
in a conversation object. Skygear Chat provides fetchParticipants
API to retrieve SKYParticipant
objects from participant IDs. Each SKYParticipant
object contains an user record. For example,
SKYContainer.default()
.chatExtension?
.fetchParticipants(
participantIDs: conversation.participantIds,
completion: {[weak self] (result, isCached, error) in
guard error == nil else {
print(error.localizedDescription)
return
}
result.forEach({ (eachParticipantID, eachParticipant) in
print(eachParticipant.record["username"])
})
})
The above function gets participant objects and outputs the username of each participant object. SKYParticipant
is fetched from skygear server, or from device cache if cache is available. isCached
is true
if data is loaded from cache.
Managing conversation participants
At some point of your conversation, you may wish to update the participant list. You may add or remove participants in a conversation.
Adding users to conversation
You can add users to an existing conversation with addParticipantsWithUserIDs:toConversation:completion:
. In the following code, userBen
, userCharles
, userDavid
and userEllen
will be added to conversation
.
SKYContainer.default().chatExtension?.addParticipants(
userIDs: [userBen, userCharles, userDavid, userEllen],
to: conversation,
completion: { (conversation, error) in
if error != nil {
print ("Unable to add the users. " +
"Error:\(error.localizedDescription)")
return
}
print ("Users added to the conversation.")
})
Removing users from conversation
To remove users from a conversation, you can call removeParticipantsWithUserIDs:fromConversation:completion:
. In the following code, userBen
and userCharles
will be removed from conversation
.
SKYContainer.default().chatExtension?.removeParticipants(
userIDs: [userBen, userCharles],
from: conversation,
completion: { (conversation, error) in
if error != nil {
print ("Unable to remove the users. " +
"Error:\(error.localizedDescription)")
return
}
print ("Users removed from the conversation")
})
Admin
An admin of the conversation has the following permissions:
- add or remove participants from to conversation,
- add or remove admins from the conversation and
- delete the conversation
The number of admins in a conversation is unlimited, so you may add everyone as an admin.
Adding admins
You can add admins to a conversation via calling addAdminsWithUserIDs:toConversation:completion:
. In the following example, userDavid
and userEllen
are added as admins of the conversation.
SKYContainer.default().chatExtension?.addAdmins(
userIDs: [userDavid, userEllen],
to: conversation,
completion: { (conversation, error) in
if error != nil {
print("Unable to add the admins. " +
"Error:\(error.localizedDescription)")
return
}
print("Admins added")
})
Removing admins
To remove admins from a conversation, use removeAdminsWithUserIDs:fromConversation:completion:
. In the following example, userDavid
and userEllen
are no longer admins.
SKYContainer.default().chatExtension?.removeAdmins(
userIDs: [userDavid, userEllen],
from: conversation,
completion: { (conversation, error) in
if error != nil {
print("Unable to remove the admins. " +
"Error:\(error.localizedDescription)")
return
}
print("Admins removed")
})
Messages
Skygear Chat supports real time messaging. A message is the real content of a conversation. Skygear Chat supports 2 types of messages, one is plain text, the other one is assets. Assets include files, images, voice message and video.
Loading messages from a conversation
When users get into the chatroom, you may call fetchMessagesWithConversation:limit:beforeTime:order:completion:
to load the messages of the conversation. You can specify the limit of the messages in limit
and the time constraint for the message in beforeTime
.
The completion function would get called twice, once from cache and once from server. There is a boolean flag isCached
in the completion function parameter reflecting if the messages are fetched from local cache or server.
SKYContainer.default().chatExtension?.fetchMessages(
conversation: conversation,
limit: 100,
beforeTime: nil,
order: nil,
completion: { (messages, isCached, error) in
if error != nil {
print ("Messages cannot be fetched. " +
"Error:\(error.localizedDescription)")
}
if isCached {
print ("Messages fetched from cache")
}
print ("Messages fetched")
})
Sending messages
A message in Skygear Chat is a SKYMessage
record.
To send a text message, just create a SKYMessage
and specify the body
of your message. addMessage:toConversation:completion:
.
let message = SKYMessage()
message.body = "Hello!"
SKYContainer.default().chatExtension?.addMessage(message,
to: conversation) { (message, error) in
if let err = error {
// The message cannot be added, you should handle
// this error.
print("Send message error: \(err.localizedDescription)")
return
}
// The message is added to server.
// This is a good time to update your messages view with the added
// message.
updateMessagesView(message)
}
When adding a message to a conversation, the operation is created and is added to the local cache store. The completion handler is called when the message is added to server.
Handling failed messages
If messages failed to save, you can obtain them from
fetchOutstandingMessageOperations(conversationID:operationType:completion:)
.
Outstanding message operations contain messages that are not yet synchronized to the server. You can fetch outstanding message operations to display to the user which messages are failed to synchronize, so that you can present user with options whether to retry or cancel the operation.
Here is an example:
SKYContainer.default().chatExtension?.fetchOutstandingMessageOperations(
conversationID: self.conversation!.recordID().recordName,
operationType: SKYMessageOperationType.add
completion: { (operations) in
// Handle operation here. A typical example is to show
// these messages alongside sent ones.
for operation in operations {
addFailedMessagesToView(operation.message)
}
})
To retry a message operation, calls retry(messageOperation:completion:)
:
SKYContainer.default().chatExtension?.fetchOutstandingMessageOperations(
messageID: message.recordID().recordName,
operationType: SKYMessageOperationType.add
completion: { (operations) in
guard let operation = operations.first else {
return
}
SKYContainer.default().chatExtension?.retry(messageOperation: operation, completion: nil)
})
Calls cancel(messageOperation:)
to cancel the message operation instead.
Plain Text
To send a text message, just create a SKYMessage
and specify the body
of your message.
Then you can use the addMessage:toConversation:completion:
send a conversation (it works for both direct conversations or group conversations).
let message = SKYMessage()
message.body = "Hello!"
SKYContainer.default().chatExtension?.addMessage(message,
to: conversation) { (message, error) in
if let err = error {
print("Send message error: \(err.localizedDescription)")
return
}
if message != nil {
print("Send message successful")
}
}
Metadata
Besides the body of the message, you may wish to specify metadata in your message. For example, special format or color of your message.
metadata
can contain a JSON format object
let message = SKYMessage()
message.body = "Hello! See this photo!"
message.metadata = {"text-color":"#ff0000"}
// Then send the message
Files
If you would like to send files via Skygear Chat, you can upload a file as SKYAsset
.
guard let asset = SKYAsset(data: imageData) else {
return
}
SKYContainer.default().uploadAsset(asset) { (uploadedAsset, error) in
if error != nil {
print ("Upload Asset failed. " +
"Error:\(error.localizedDescription)")
return
}
// Send the message after uploading the asset
}
Editing a message
You can edit a message with editMessage:withBody:completion:
.
SKYContainer.default().editMessage(message, with: newMessageBody, completion: { (result, error) in
if let err = error {
print(err.localizedDescription)
return
}
print("Edit message successful")
})
Deleting a message
You can delete a message with deleteMessage:inConversation:completion:
.
SKYContainer.default().chatExtension?.deleteMessage(message, in: conversation) { (conversation, error) in
if let err = error {
print(err.localizedDescription)
return
}
print("Message Deleted.")
}
Subscribing to new messages
Subscribing to messages in a conversation
In order to get real time update of new messages, you can subscribe to a conversation with subscribeToMessagesInConversation:handler:
SKYContainer.default().chatExtension?.subscribeToMessages(
in: conversation,
handler: { (event, message) in
print("Received message event")
})
Subscribing to messages in all conversations
Besides a specific conversation, you might want to get notified whenever there are new messages in any conversation you belong to.
You can subscribe to all messages in your own user channel with subscribeToUserChannelWithCompletion:
SKYContainer.default().chatExtension?.subscribeToUserChannelWithCompletion(
completion: { (error) in
print("Received message event")
})
Callback data
The callback passes a data object as follows:
{
"record_type": "message",
"event_type": "create",
"record": recordObj,
"original_record": nulll
}
The event_type may contain the following strings:
- create - new message received from others, and it should be inserted to your conversation UI
- update - when a message updated, or if the delivery or read status change (e.g. from
delivered
tosome_read
atmessage_status
) - delete - when a message was deleted
Push notification
Sending push notifications to conversation participants
You can send the push notification to particular userIds
and these can be retrieved by accessing the attribute participantsIds
of SKYConversation
.
let apsInfo = SKYAPSNotificationInfo()
apsInfo.alertBody = text
let info = SKYNotificationInfo()
info.apsNotificationInfo = apsInfo
let operation = SKYSendPushNotificationOperation(
notificationInfo: info,
userIDsToSend: conversation.participantIds)
operation?.sendCompletionHandler = { (userIds, error) in
if error != nil {
print("Error on sending push notification. " +
"Error: \(error?.localizedDescription)")
return
}
if let userIds = userIds {
print ("Sent notifications to \(userIds.count)")
}
}
SKYContainer.default().add(operation)
Receiving push notifications
Implement the handling of push notification in AppDelegate
:
func application(_ application: UIApplication, didReceiveRemoteNotification
userInfo: [AnyHashable : Any],
fetchCompletionHandler completionHandler:
@escaping (UIBackgroundFetchResult) -> Void) {
let aps = userInfo["aps"] as! [String: AnyObject]
print ("Received push notification: \(aps["alert"]?["body"])")
}
Receipt
Skygear Chat provides you with message receipt which includes status and timestamps information.
Message status
You can make use of the following receipt status to indicate your message status.
SKYMessageConversationStatus
has 4 values.
Status | Description |
---|---|
SKYChatReceiptStatusDelivering |
The message is being delivered, it is not yet received by the other party. |
SKYChatReceiptStatusDelivered |
The message is delivered, but it is not read yet. |
SKYChatReceiptStatusRead |
The message is delivered and read by some participants. |
SKYMessageConversationStatusAllRead |
The message is delivered and read by all participants. |
Subscribing to message status change
By subscribing to SKYChatReceiptStatus
in a message, you can get the latest status of the message sent to other recipients.
switch (message.conversationStatus) {
case .allRead:
print ("Message read by all.")
case .someRead:
print ("Message read by some participants.")
case .delivered:
print ("Message delivered.")
case .delivering:
print ("Message is delivering.")
}
Marking messages as read
On the recipient client side, you need to update the message status if the message is read. For example, in viewDidAppear
method of your message view controller.
SKYContainer.default().chatExtension?.markReadMessages(messages,
completion: { (error) in
if error != nil {
print("Error on marking messages as read. " +
"Error: \(error.localizedDescription)")
return
}
print("Messages are marked as read")
})
Note: messages
is an array of SKYMessage
.
Message unread count
Conversation unread count
You can show the unread count for different conversations in the conversation list.
SKYContainer.default().chatExtension?.fetchUnreadCount(
conversation: conversation,
completion: { (dict, error) in
if error != nil {
print ("Unable to get the unread count. " +
"Error: \(error.localizedDescription)")
return
}
if let unreadMessages = dict?["message"]?.intValue {
print ("Unread count: \(unreadMessages)")
}
})
Overall unread count
You may wish to show the overall unread count of all conversations in the badge value of your app.
SKYContainer.default().chatExtension?.fetchTotalUnreadCount(
completion: { (dict, error) in
if error != nil {
print ("Unable to get the total unread count. " +
"Error: \(error.localizedDescription)")
return
}
if let unreadMessages = dict?["message"]?.intValue {
print ("Total unread count: \(unreadMessages)")
}
})
Resetting the unread count
The unread count can be reset by marking the messages as read.
Typing indicator
The SKYChatTypingEvent
has these events:
SKYChatTypingEventBegin
- User began typingSKYChatTypingEventPause
- User stopped typingSKYChatTypingEventFinished
- User stopped typing and the message is sent
You can make good use of these events to implement the typing indicator feature in your app.
Subscribing to typing indicator
Skygear Chat provides real-time update to typing indicators in a particular conversation.
subscribeToTypingIndicatorInConversation:handler:
SKYContainer.default().chatExtension?.subscribeToTypingIndicator(
in: conversation,
handler: { (indicator) in
print("Receiving typing event")
})
Sending an User's typing status
To get typing status from other devices, you should always update your typing status to the server with subscribeToTypingIndicatorInConversation:handler:
.
SKYContainer.default().chatExtension?.sendTypingIndicator(.begin, in: conversation)
SKYContainer.default().chatExtension?.sendTypingIndicator(.pause, in: conversation)
SKYContainer.default().chatExtension?.sendTypingIndicator(.finished, in: conversation)
Most app developers should call the method: sendTypingIndicator(_:in:at:completion:)
SKYContainer.default().chatExtension?.sendTypingIndicator(event,
in: (conversation?conversation)!,
at: Date()) { (error) in
if error != nil {
print ("Error on sending typing indicator. " +
"Error: \(error.localizedDescription)")
return
}
print ("Sent typing indicator")
}
with the current date Date()
in the at
parameter.
User online
Coming soon