1. Basic Conversations and Messages
Introduction
A lot of products today have the need to offer instant messaging functions to their users. For example:
- To have the staff behind the product talk to the users.
- To have the employees of a company communicate with each other.
- To have the audience of a live stream interact with each other.
- To have the users of an app or players of a game chat with each other.
Based on the hierarchy of needs and the difficulty of implementation, we wrote four chapters of documentation for you to learn how you can embed Instant Messaging into your app:
- In this chapter, we will introduce how you can implement one-on-one chats and group chats, how you can create and join conversations, and how you can send and receive rich media messages. We will also cover how history messages are kept on the cloud and how you can retrieve them. By the end of this chapter, you should be able to build a simple chatting page in your app.
- In the second chapter, we will introduce some advanced features built around messaging, including mentioning people with "@", recalling messages, editing messages, getting receipts when messages are delivered and read, sending push notifications, and synchronizing messages. The implementation of multi-device sign-on and custom message types will also be covered. By the end of this chapter, you should be able to integrate a chatting component into your app with these features.
- In the third chapter, we will introduce the security features offered by our services, including third-party signing mechanism, permission management of members, and blacklisting. We will also go over the usage of chat rooms and temporary conversations. By the end of this chapter, you will get a set of skills to improve the security and usability of your app, as well as to build conversations that serve different purposes.
- In the last chapter, we will introduce the usage of hooks and system conversations, plus how you can build your own chatbots based on them. By the end of this chapter, you will learn how you can make your app extensible and adapted to a wide variety of requirements.
We aim our documentation to not only help you complete the functions you are currently building but also give you a better understanding of all the things Instant Messaging can do, which you will find helpful when you plan to add more features to your app.
Before you continue:
Take a look at Instant Messaging Overview if you haven't done it yet. Also, make sure you have already installed and initialized the SDK for the platform (language) you are using:
One-on-One Chats
Before diving into the main topic, let's see what an IMClient
object is in the Instant Messaging SDK:
An
IMClient
refers to an actual user, meaning that the user logged in to the system as a client.
See Instant Messaging Overview for more details.
Creating IMClient
Assuming that there is a user named "Tom". Now let's create an IMClient
instance for him (make sure you have already initialized the SDK):
- Unity
- Android
- iOS
- JavaScript
LCIMClient tom = new LCIMClient("Tom");
// clientId is Tom
LCIMClient tom = LCIMClient.getInstance("Tom");
// Define a property variable that persists in the memory
@property (nonatomic) LCIMClient *tom;
// Initialization
NSError *error;
tom = [[LCIMClient alloc] initWithClientId:@"Tom" error:&error];
if (error) {
NSLog(@"init failed with error: %@", error);
} else {
NSLog(@"init succeeded");
}
// Tom logs in with his name as clientId
realtime
.createIMClient("Tom")
.then(function (tom) {
// Successfully logged in
})
.catch(console.error);
Keep in mind that an IMClient
refers to an actual user. It should be stored globally since all the further actions done by this user will have to access it.
Logging in to the Instant Messaging Server
After creating the IMClient
instance for Tom, we will need to have this instance log in to the Instant Messaging server.
Only clients that are logged in can chat with other users and receive notifications from the cloud.
For some SDKs (like the C# SDK), the client will be automatically logged in when the IMClient
instance is created; for others (like iOS and Android SDKs), the client needs to be logged in manually with the open
method:
- Unity
- Android
- iOS
- JavaScript
await tom.Open();
// Tom creates a client and logs in with his name as clientId
LCIMClient tom = LCIMClient.getInstance("Tom");
// Tom logs in
tom.open(new LCIMClientCallback() {
@Override
public void done(LCIMClient client, LCIMException e) {
if (e == null) {
// Successfully connected
}
}
});
// Define a property variable that persists in the memory
@property (nonatomic) LCIMClient *tom;
// Initialize and log in
NSError *error;
tom = [[LCIMClient alloc] initWithClientId:@"Tom" error:&error];
if (error) {
NSLog(@"init failed with error: %@", error);
} else {
[tom openWithCallback:^(BOOL succeeded, NSError * _Nullable error) {
if (succeeded) {
// open succeeded
}
}];
}
// Tom logs in with his name as clientId and gets the IMClient instance
realtime
.createIMClient("Tom")
.then(function (tom) {
// Successfully logged in
})
.catch(console.error);
Logging in with _User
Besides specifying a clientId
on the application layer, you can also log in directly by creating an IMClient
with a _User
object. By doing so, the signing process for logging in can be skipped which helps you easily integrate Data Storage with Instant Messaging:
- Unity
- Android
- iOS
- JavaScript
var user = await LCUser.Login("USER_NAME", "PASSWORD");
var client = new LCIMClient(user);
//Log in to the Data Storage service with the username and password of an LCUser
LCUser.logIn("Tom", "cat!@#123").subscribe(new Observer<LCUser>() {
public void onSubscribe(Disposable disposable) {}
public void onNext(LCUser user) {
// Logged in successfully and connected to the server
LCIMClient client = LCIMClient.getInstance(user);
client.open(new LCIMClientCallback() {
@Override
public void done(final LCIMClient avimClient, LCIMException e) {
// Do other stuff
}
});
}
public void onError(Throwable throwable) {
// Failed to log in (possibly because the password is incorrect)
}
public void onComplete() {}
});
// Define a property variable that persists in the memory
@property (nonatomic) LCIMClient *client;
// Log the User in and use the logged in User to initialize the Client and log in to the Instant Messaging service
[LCUser logInWithUsernameInBackground:USER_NAME password:PASSWORD block:^(LCUser * _Nullable user, NSError * _Nullable error) {
if (user) {
NSError *err;
client = [[LCIMClient alloc] initWithUser:user error:&err];
if (err) {
NSLog(@"init failed with error: %@", err);
} else {
[client openWithCallback:^(BOOL succeeded, NSError * _Nullable error) {
if (succeeded) {
// open succeeded
}
}];
}
}
}];
var AV = require("leancloud-storage");
// Log in to Instant Messaging with the username and password of an AVUser
AV.User.logIn("username", "password")
.then(function (user) {
return realtime.createIMClient(user);
})
.catch(console.error.bind(console));
Creating Conversations
A Conversation
needs to be created before a user can chat with others.
Conversation
s are the carriers of messages. All the messages are sent to conversations to be delivered to the members in them.
Since Tom is already logged in, he can start chatting with other users now. If he wants to chat with Jerry, he can create a Conversation
containing Jerry and himself:
- Unity
- Android
- iOS
- JavaScript
var conversation = await tom.CreateConversation(new string[] { "Jerry" }, name: "Tom & Jerry", unique: true);
tom.createConversation(Arrays.asList("Jerry"), "Tom & Jerry", null, false, true,
new LCIMConversationCreatedCallback() {
@Override
public void done(LCIMConversation conversation, LCIMException e) {
if(e == null) {
// Successfully created
}
}
});
// Create a conversation with Jerry
[self createConversationWithClientIds:@[@"Jerry"] callback:^(LCIMConversation * _Nullable conversation, NSError * _Nullable error) {
// handle callback
}];
// Create a conversation with Jerry
tom
.createConversation({
// tom is an IMClient instance
// Members of the conversation include Jerry (the SDK will automatically add the current user into the conversation) besides Tom
members: ["Jerry"],
// The name of the conversation
name: "Tom & Jerry",
unique: true,
})
.then(/* Do other stuff */);
createConversation
creates a new conversation and stores it into the _Conversation
table which can be found on Developer Center > Your game > Game Services > Cloud Services > Data Storage > Data. Below are the interfaces offered by different SDKs for creating conversations:
- Unity
- Android
- iOS
- JavaScript
/// <summary>
/// Creates a conversation
/// </summary>
/// <param name="members">The list of clientIds of participants in this conversation (except the creator)</param>
/// <param name="name">The name of this conversation</param>
/// <param name="unique">Whether this conversation is unique;
/// if it is true and an existing conversation contains the same composition of members,
/// the existing conversation will be reused, otherwise a new conversation will be created.</param>
/// <param name="properties">Custom attributes of this conversation</param>
/// <returns></returns>
public async Task<LCIMConversation> CreateConversation(
IEnumerable<string> members,
string name = null,
bool unique = true,
Dictionary<string, object> properties = null) {
return await ConversationController.CreateConv(members: members,
name: name,
unique: unique,
properties: properties);
}
/**
* Create or find an existing conversation
*
* @param members The members of the conversation
* @param name The name of the conversation
* @param attributes Custom attributes
* @param isTransient Whether the conversation is a chat room
* @param isUnique Whether to return the existing conversation satisfying conditions
* If false, create a new conversation
* If true, find if there is an existing conversation satisfying conditions; if so, return the conversation, otherwise create a new conversation
* If true, only members is the valid query condition
* @param callback The callback after the conversation is created
*/
public void createConversation(final List<String> members, final String name,
final Map<String, Object> attributes, final boolean isTransient, final boolean isUnique,
final LCIMConversationCreatedCallback callback);
/**
* Create a conversation
*
* @param members The members of the conversation
* @param attributes Custom attributes
* @param isTransient Whether the conversation is a chat room
* @param callback The callback after the conversation is created
*/
public void createConversation(final List<String> members, final String name,
final Map<String, Object> attributes, final boolean isTransient,
final LCIMConversationCreatedCallback callback);
/**
* Create a conversation
*
* @param conversationMembers The members of the conversation
* @param name The name of the conversation
* @param attributes Custom attributes
* @param callback The callback after the conversation is created
* @since 3.0
*/
public void createConversation(final List<String> conversationMembers, String name,
final Map<String, Object> attributes, final LCIMConversationCreatedCallback callback);
/**
* Create a conversation
*
* @param conversationMembers The members of the conversation
* @param attributes Custom attributes
* @param callback The callback after the conversation is created
* @since 3.0
*/
public void createConversation(final List<String> conversationMembers,
final Map<String, Object> attributes, final LCIMConversationCreatedCallback callback);
/// The option of conversation creation.
@interface LCIMConversationCreationOption : NSObject
/// The name of the conversation.
@property (nonatomic, nullable) NSString *name;
/// The attributes of the conversation.
@property (nonatomic, nullable) NSDictionary *attributes;
/// Create or get an unique conversation, default is `true`.
@property (nonatomic) BOOL isUnique;
/// The time interval for the life of the temporary conversation.
@property (nonatomic) NSUInteger timeToLive;
@end
/// Create a Normal Conversation. Default is a Normal Unique Conversation.
/// @param clientIds The set of client ID. it's the members of the conversation which will be created. the initialized members always contains the current client's ID. if the created conversation is unique, and the server has one unique conversation with the same members, that unique conversation will be returned.
/// @param callback Result callback.
- (void)createConversationWithClientIds:(NSArray<NSString *> *)clientIds
callback:(void (^)(LCIMConversation * _Nullable conversation, NSError * _Nullable error))callback;
/// Create a Normal Conversation. Default is a Normal Unique Conversation.
/// @param clientIds The set of client ID. it's the members of the conversation which will be created. the initialized members always contains the current client's ID. if the created conversation is unique, and the server has one unique conversation with the same members, that unique conversation will be returned.
/// @param option See `LCIMConversationCreationOption`.
/// @param callback Result callback.
- (void)createConversationWithClientIds:(NSArray<NSString *> *)clientIds
option:(LCIMConversationCreationOption * _Nullable)option
callback:(void (^)(LCIMConversation * _Nullable conversation, NSError * _Nullable error))callback;
/// Create a Chat Room.
/// @param callback Result callback.
- (void)createChatRoomWithCallback:(void (^)(LCIMChatRoom * _Nullable chatRoom, NSError * _Nullable error))callback;
/// Create a Chat Room.
/// @param option See `LCIMConversationCreationOption`.
/// @param callback Result callback.
- (void)createChatRoomWithOption:(LCIMConversationCreationOption * _Nullable)option
callback:(void (^)(LCIMChatRoom * _Nullable chatRoom, NSError * _Nullable error))callback;
/// Create a Temporary Conversation. Temporary Conversation is unique in its Life Cycle.
/// @param clientIds The set of client ID. it's the members of the conversation which will be created. the initialized members always contains this client's ID.
/// @param callback Result callback.
- (void)createTemporaryConversationWithClientIds:(NSArray<NSString *> *)clientIds
callback:(void (^)(LCIMTemporaryConversation * _Nullable temporaryConversation, NSError * _Nullable error))callback;
/// Create a Temporary Conversation. Temporary Conversation is unique in its Life Cycle.
/// @param clientIds The set of client ID. it's the members of the conversation which will be created. the initialized members always contains this client's ID.
/// @param option See `LCIMConversationCreationOption`.
/// @param callback Result callback.
- (void)createTemporaryConversationWithClientIds:(NSArray<NSString *> *)clientIds
option:(LCIMConversationCreationOption * _Nullable)option
callback:(void (^)(LCIMTemporaryConversation * _Nullable temporaryConversation, NSError * _Nullable error))callback;
/**
* Create a conversation
* @param {Object} options The fields beside the following ones will be treated as custom attributes
* @param {String[]} options.members The members of the conversation; required; include the current client by default
* @param {String} [options.name] The name of the conversation; optional; defaults to null
* @param {Boolean} [options.transient=false] Whether the conversation is a chat room; optional
* @param {Boolean} [options.unique=false] Whether the conversation is unique; if it is true and an existing conversation contains the same composition of members, the existing conversation will be reused, otherwise a new conversation will be created
* @param {Boolean} [options.tempConv=false] Whether the conversation is temporary; optional
* @param {Integer} [options.tempConvTTL=0] Optional; if tempConv is true, the TTL of the conversation can be specified here
* @return {Promise.<Conversation>}
*/
async createConversation({
members: m,
name,
transient,
unique,
tempConv,
tempConvTTL,
// You may add more properties
});
Although SDKs for different languages/platforms share different interfaces, they take in a similar set of parameters when creating a conversation:
members
: Required; includes the initial list of members in the conversation. The creator of the conversation is included by default, somembers
does not have to include theclientId
of the current user.name
: The name of the conversation; optional. The code above sets "Tom & Jerry" for it.attributes
: The custom attributes of the conversation; optional. The code above does not specify any custom attributes. If you ever specify them for your conversations, you can retrieve them later withLCIMConversation
. Such attributes will be stored in theattr
field of the_Conversation
table.unique
/isUnique
orLCIMConversationOptionUnique
: Marks if the conversation is unique; optional.- If true, the cloud will perform a query on conversations with the list of members specified. If an existing conversation contains the same members, the conversation will be returned, otherwise a new conversation will be created.
- If false, a new conversation will be created each time
createConversation
is called. - If not specified, it defaults to true.
- In general, it is more reasonable that there is only one conversation existing for the same composition of members, otherwise it could be messy since multiple sets of message histories are available for the same group of people.
Other parameters specifying the type of the conversation; optional. For example,
transient
/isTransient
specifies if it is a chat room, andtempConv
/tempConvTTL
orLCIMConversationOptionTemporary
specifies if it is a temporary conversation. If nothing is specified, it will be a basic conversation. We will talk more about them later.
The built-in properties of a conversation can be retrieved once the conversation is created. For example, a globally unique ID will be created for each conversation which can be retrieved with Conversation.id
. This is the field often used for querying conversations.
Sending Messages
Now that the conversation is created, Tom can start sending messages to it:
- Unity
- Android
- iOS
- JavaScript
var textMessage = new LCIMTextMessage("Get up, Jerry!");
await conversation.Send(textMessage);
LCIMTextMessage msg = new LCIMTextMessage();
msg.setText("Get up, Jerry!");
// Send the message
conversation.sendMessage(msg, new LCIMConversationCallback() {
@Override
public void done(LCIMException e) {
if (e == null) {
Log.d("Tom & Jerry", "Message sent!");
}
}
});
LCIMTextMessage *message = [LCIMTextMessage messageWithText:@"Get up, Jerry!" attributes:nil];
[conversation sendMessage:message callback:^(BOOL succeeded, NSError *error) {
if (succeeded) {
NSLog(@"Message sent!");
}
}];
var { TextMessage } = require("leancloud-realtime");
conversation
.send(new TextMessage("Get up, Jerry!"))
.then(function (message) {
console.log("Tom & Jerry", "Message sent!");
})
.catch(console.error);
The code above sends a message to the conversation specified. All the other members who are online will immediately receive the message.
So how would Jerry see the message on his device?
Receiving Messages
On another device, we create an IMClient
with Jerry
as clientId
and log in to the server (just as how we did for Tom):
- Unity
- Android
- iOS
- JavaScript
var jerry = new LCIMClient("Jerry");
// Jerry logs in
LCIMClient jerry = LCIMClient.getInstance("Jerry");
jerry.open(new LCIMClientCallback(){
@Override
public void done(LCIMClient client,LCIMException e){
if(e==null){
// Things to do after logging in
}
}
});
NSError *error;
jerry = [[LCIMClient alloc] initWithClientId:@"Jerry" error:&error];
if (!error) {
[jerry openWithCallback:^(BOOL succeeded, NSError *error) {
// handle callback
}];
}
var { Event } = require("leancloud-realtime");
// Jerry logs in
realtime
.createIMClient("Jerry")
.then(function (jerry) {})
.catch(console.error);
As the receiver of the message, Jerry doesn't have to create a conversation with Tom and may as well not know that Tom created a conversation with him. Jerry needs to set up a callback function to get notified of the things Tom did.
By setting up callbacks, clients will be able to handle notifications sent from the cloud. Here we focus on the following two events:
- The user is invited to a conversation. At the moment Tom creates a new conversation with Jerry, Jerry will receive a notification saying something like "Tom invited you to a conversation".
- A new message is delivered to a conversation the user is already in. At the moment Tom sends out the message "Get up, Jerry!", Jerry will receive a notification including the message itself as well as the context information like the conversation the message is sent to and the sender of the message.
Now let's see how clients should handle such notifications. The code below handles both "joining conversation" and "getting new message" events for Jerry:
- Unity
- Android
- iOS
- JavaScript
jerry.OnInvited = (conv, initBy) => {
WriteLine($"{initBy} invited Jerry to join the conversation {conv.Id}");
};
jerry.OnMessage = (conv, msg) => {
if (msg is LCIMTextMessage textMessage) {
// textMessage.ConversationId is the ID of the conversation the message belongs to
// textMessage.Text is the content of the text message
// textMessage.FromClientId is the clientId of the sender
}
};
// Java/Android SDK responds to notifications with custom event handlers
public class CustomConversationEventHandler extends LCIMConversationEventHandler {
/**
* This method is implemented to handle the events when the current user is invited to a conversation
*
* @param client
* @param conversation The conversation
* @param operator The inviter
* @since 3.0
*/
@Override
public void onInvited(LCIMClient client, LCIMConversation conversation, String invitedBy) {
// Things to do after the current clientId (Jerry) is invited to the conversation
}
}
// Set up global conversation event handler
LCIMMessageManager.setConversationEventHandler(new CustomConversationEventHandler());
// Java/Android SDK responds to notifications with custom event handlers
public static class CustomMessageHandler extends LCIMMessageHandler{
/**
* Overloading this method to handle message receiving
*
* @param message
* @param conversation
* @param client
*/
@Override
public void onMessage(LCIMMessage message,LCIMConversation conversation,LCIMClient client){
if(message instanceof LCIMTextMessage){
Log.d(((LCIMTextMessage)message).getText()); // Get up, Jerry!
}
}
}
// Set up global message handling handler
LCIMMessageManager.registerDefaultMessageHandler(new CustomMessageHandler());
// Implement the LCIMClientDelegate delegate to respond to the notifications from the server
// For those unfamiliar with the concept of delegation, please refer to:
// https://developer.apple.com/library/archive/documentation/General/Conceptual/CocoaEncyclopedia/DelegatesandDataSources/DelegatesandDataSources.html
jerry.delegate = delegator;
/*!
The current user is added to a conversation
@param conversation - The conversation
@param clientId - The ID of the inviter
*/
- (void)conversation:(LCIMConversation *)conversation invitedByClientId:(NSString *)clientId {
NSLog(@"%@", [NSString stringWithFormat:@"The current clientId (Jerry) is invited by %@ to join the conversation.",clientId]);
}
/*!
The current user receives a message
@param conversation - The conversation
@param message - The content of the message
*/
- (void)conversation:(LCIMConversation *)conversation didReceiveTypedMessage:(LCIMTypedMessage *)message {
NSLog(@"%@", message.text); // Get up, Jerry!
}
// JS SDK responds to notifications by listening to event callbacks on the IMClient instance
// The current user is added to a conversation
jerry.on(Event.INVITED, function invitedEventHandler(payload, conversation) {
console.log(payload.invitedBy, conversation.id);
});
// The current user receives a message; can be handled by responding to Event.MESSAGE
jerry.on(Event.MESSAGE, function (message, conversation) {
console.log("Message received: " + message.text);
});
With the two event handling functions above, Jerry will be able to receive messages from Tom. Jerry can send messages to Tom as well, as long as Tom has the same functions on his side.
Now let's take a look at this sequence diagram showing how the first message sent from Tom to Jerry is processed:
Besides responding to notifications about new messages, clients also need to respond to those indicating the change of members in a conversation, like "XX invited XX into the conversation", "XX left the conversation", and "XX is removed by the admin". Such notifications will be delivered to clients in real time. See Summary of Event Notifications Regarding Changes of Members for more details.
Group Chats
We just discussed how we can create a conversation between two users. Now let's see how we can create a group chat with more people.
There aren't many differences between the two types of conversations and a major one would be the number of members in them. You can either specify all the members of a group chat when creating it, or add them later after the conversation is created.
Creating Group Chats
In the previous conversation between Tom and Jerry (assuming conversation ID to be CONVERSATION_ID
), if Tom wants to add Mary into the conversation, the following code can be used:
- Unity
- Android
- iOS
- JavaScript
// Get the conversation with ID
var conversation = await tom.GetConversation("CONVERSATION_ID");
// Invite Mary
await conversation.AddMembers(new string[] { "Mary" });
// Get the conversation with ID
final LCIMConversation conv = client.getConversation("CONVERSATION_ID");
// Invite Mary
conv.addMembers(Arrays.asList("Mary"), new LCIMOperationPartiallySucceededCallback() {
@Override
public void done(LCIMException e, List<String> successfulClientIds, List<LCIMOperationFailure> failures) {
// Member added
}
});
// Get the conversation with ID
LCIMConversationQuery *query = [self.client conversationQuery];
[query getConversationById:@"CONVERSATION_ID" callback:^(LCIMConversation *conversation, NSError *error) {
// Invite Mary
[conversation addMembersWithClientIds:@[@"Mary"] callback:^(BOOL succeeded, NSError *error) {
if (succeeded) {
NSLog(@"Member added!");
}
}];
}];
// Get the conversation with ID
tom
.getConversation("CONVERSATION_ID")
.then(function (conversation) {
// Invite Mary
return conversation.add(["Mary"]);
})
.then(function (conversation) {
console.log("Member added!", conversation.members);
// The conversation now contains ['Mary', 'Tom', 'Jerry']
})
.catch(console.error.bind(console));
On Jerry's side, he can add a listener for handling events regarding "new members being added". With the code below, he will be notified once Tom invites Mary to the conversation:
- Unity
- Android
- iOS
- JavaScript
jerry.OnMembersJoined = (conv, memberList, initBy) => {
WriteLine($"{initBy} invited {memberList} to join the conversation {conv.Id}");
}
AVIMOnInvitedEventArgs
contains the following fields:
InvitedBy
: The inviterJoinedMembers
: The list of members being addedConversationId
: The conversation
public class CustomConversationEventHandler extends LCIMConversationEventHandler {
/**
* This method is implemented to handle the events when a new member joins a conversation
*
* @param client
* @param conversation
* @param members The list of new members
* @param invitedBy The ID of the inviter; could be the new member itself
* @since 3.0
*/
@Override
public void onMemberJoined(LCIMClient client, LCIMConversation conversation,
List<String> members, String invitedBy) {
// Shows that Mary is added to 551260efe4b01608686c3e0f by Tom
Toast.makeText(LeanCloud.applicationContext,
members + " is added to " + conversation.getConversationId() + " by "
+ invitedBy, Toast.LENGTH_SHORT).show();
}
}
// Set up global event handler
LCIMMessageManager.setConversationEventHandler(new CustomConversationEventHandler());
jerry.delegate = delegator;
#pragma mark - LCIMClientDelegate
/*!
All members will receive a notification when a new member joins the conversation
@param conversation - The conversation
@param clientIds - The list of new members
@param clientId - The ID of the inviter
*/
- (void)conversation:(LCIMConversation *)conversation membersAdded:(NSArray *)clientIds byClientId:(NSString *)clientId {
NSLog(@"%@", [NSString stringWithFormat:@"%@ is added to the conversation by %@",[clientIds objectAtIndex:0],clientId]);
}
// A user is added to the conversation
jerry.on(
Event.MEMBERS_JOINED,
function membersjoinedEventHandler(payload, conversation) {
console.log(payload.members, payload.invitedBy, conversation.id);
}
);
payload
contains the following fields:
members
: Array of strings; the list ofclientId
s of the members being addedinvitedBy
: String; theclientId
of the inviter
Here is the sequence diagram of the operation:
On Mary's side, to know that she is added to the conversation between Tom and Jerry, she can follow the way Jerry listens to the INVITED
event, which can be found in One-on-One Chats.
If Tom wants to create a new conversation with all the members included, the following code can be used:
- Unity
- Android
- iOS
- JavaScript
var conversation = await tom.CreateConversation(new string[] { "Jerry","Mary" }, name: "Tom & Jerry & friends", unique: true);
tom.createConversation(Arrays.asList("Jerry","Mary"), "Tom & Jerry & friends", null,
new LCIMConversationCreatedCallback() {
@Override
public void done(LCIMConversation conversation, LCIMException e) {
if (e == null) {
// Conversation created
}
}
});
// Tom creates a conversation with his friends
[tom createConversationWithClientIds:@[@"Jerry", @"Mary"] callback:^(LCIMConversation * _Nullable conversation, NSError * _Nullable error) {
if (!error) {
NSLog(@"Conversation created!");
}
}];
tom
.createConversation({
// Add Jerry and Mary to the conversation when creating it; more members can be added later as well
members: ["Jerry", "Mary"],
// The name of the conversation
name: "Tom & Jerry & friends",
unique: true,
})
.catch(console.error);
Sending Group Messages
In a group chat, if a member sends a message, the message will be delivered to all the online members in the group. The process is the same as how Jerry receives the message from Tom.
For example, if Tom sends a welcome message to the group:
- Unity
- Android
- iOS
- JavaScript
var textMessage = new LCIMTextMessage("Welcome everyone!");
await conversation.Send(textMessage);
LCIMTextMessage msg = new LCIMTextMessage();
msg.setText("Welcome everyone!");
// Send the message
conversation.sendMessage(msg, new LCIMConversationCallback() {
@Override
public void done(LCIMException e) {
if (e == null) {
Log.d("Group chat", "Message sent!");
}
}
});
[conversation sendMessage:[LCIMTextMessage messageWithText:@"Welcome everyone!" attributes:nil] callback:^(BOOL succeeded, NSError *error) {
if (succeeded) {
NSLog(@"Message sent!");
}
}];
conversation.send(new TextMessage("Welcome everyone!"));
Both Jerry and Mary will have the Event.MESSAGE
event triggered which can be used to retrieve the message and have it displayed on the UI.
Removing Members
One day Mary said something that made Tom angry and Tom wants to kick her out of the group chat. How should Tom do that?
- Unity
- Android
- iOS
- JavaScript
await conversation.RemoveMembers(new string[] { "Mary" });
conv.kickMembers(Arrays.asList("Mary"), new LCIMOperationPartiallySucceededCallback() {
@Override
public void done(LCIMException e, List<String> successfulClientIds, List<LCIMOperationFailure> failures) {
}
});
[conversation removeMembersWithClientIds:@[@"Mary"] callback:^(BOOL succeeded, NSError *error) {
if (succeeded) {
NSLog(@"Member removed!");
}
}];
conversation
.remove(["Mary"])
.then(function (conversation) {
console.log("Member removed!", conversation.members);
})
.catch(console.error.bind(console));
The following process will be triggered:
Here we see that Mary receives KICKED
which indicates that she (the current user) is removed. Other members (Jerry and Tom) will receive MEMBERS_LEFT
which indicates that someone else in the conversation is removed. Such events can be handled with the following code:
- Unity
- Android
- iOS
- JavaScript
jerry.OnMembersLeft = (conv, leftIds, kickedBy) => {
WriteLine($"{leftIds} removed from {conv.Id} by {kickedBy}");
}
jerry.OnKicked = (conv, initBy) => {
WriteLine($"You are removed from {conv.Id} by {initBy}");
};
public class CustomConversationEventHandler extends LCIMConversationEventHandler {
/**
* This method is implemented to handle the events when someone is removed from a conversation
*
* @param client
* @param conversation
* @param members The members being removed
* @param kickedBy The ID of the operator; could be the current user itself
* @since 3.0
*/
@Override
public abstract void onMemberLeft(LCIMClient client,
LCIMConversation conversation, List<String> members, String kickedBy) {
Toast.makeText(LeanCloud.applicationContext,
members + " are removed from " + conversation.getConversationId() + " by "
+ kickedBy, Toast.LENGTH_SHORT).show();
}
/**
* This method is implemented to handle the events when the current user is removed from a conversation
*
* @param client
* @param conversation
* @param kickedBy The person who removed you
* @since 3.0
*/
@Override
public abstract void onKicked(LCIMClient client, LCIMConversation conversation,
String kickedBy) {
Toast.makeText(LeanCloud.applicationContext,
"You are removed from " + conversation.getConversationId() + " by "
+ kickedBy, Toast.LENGTH_SHORT).show();
}
}
// Set up global event handler
LCIMMessageManager.setConversationEventHandler(new CustomConversationEventHandler());
jerry.delegate = delegator;
#pragma mark - LCIMClientDelegate
/*!
The remaining members in a conversation will receive this notification when someone is removed from the conversation.
@param conversation - The conversation
@param clientIds - The list of members being removed
@param clientId - The ID of the operator
*/
- (void)conversation:(LCIMConversation *)conversation membersRemoved:(NSArray<NSString *> * _Nullable)clientIds byClientId:(NSString * _Nullable)clientId {
;
}
/*!
The notification for the events when the current user is removed from a conversation.
@param conversation - The conversation
@param clientId - The ID of the operator
*/
- (void)conversation:(LCIMConversation *)conversation kickedByClientId:(NSString * _Nullable)clientId {
;
}
// Someone else is removed
jerry.on(
Event.MEMBERS_LEFT,
function membersjoinedEventHandler(payload, conversation) {
console.log(payload.members, payload.kickedBy, conversation.id);
}
);
// The current user is removed
jerry.on(
Event.KICKED,
function membersjoinedEventHandler(payload, conversation) {
console.log(payload.kickedBy, conversation.id);
}
);
Joining Conversations
Tom is feeling bored after removing Mary. He goes to William and tells him that there is a group chat that Jerry and himself are in. He gives the ID (or name) of the group chat to William which makes him curious about what's going on in it. William then adds himself to the group:
- Unity
- Android
- iOS
- JavaScript
var conv = await william.GetConversation("CONVERSATION_ID");
await conv.Join();
LCIMConversation conv = william.getConversation("CONVERSATION_ID");
conv.join(new LCIMConversationCallback(){
@Override
public void done(LCIMException e){
if(e==null){
// Successfully joined
}
}
});
LCIMConversationQuery *query = [william conversationQuery];
[query getConversationById:@"CONVERSATION_ID" callback:^(LCIMConversation *conversation, NSError *error) {
[conversation joinWithCallback:^(BOOL succeeded, NSError *error) {
if (succeeded) {
NSLog(@"Successfully joined!");
}
}];
}];
william
.getConversation("CONVERSATION_ID")
.then(function (conversation) {
return conversation.join();
})
.then(function (conversation) {
console.log("Successfully joined!", conversation.members);
// The conversation now contains ['William', 'Tom', 'Jerry']
})
.catch(console.error.bind(console));
The following process will be triggered:
Other members can listen to MEMBERS_JOINED
to know that William joined the conversation:
- Unity
- Android
- iOS
- JavaScript
jerry.OnMembersJoined = (conv, memberList, initBy) => {
WriteLine($"{memberList} joined {conv.Id}; operated by {initBy}");
}
public class CustomConversationEventHandler extends LCIMConversationEventHandler {
@Override
public void onMemberJoined(LCIMClient client, LCIMConversation conversation,
List<String> members, String invitedBy) {
// Shows that William joined 551260efe4b01608686c3e0f; operated by William
Toast.makeText(LeanCloud.applicationContext,
members + " joined " + conversation.getConversationId() + "; operated by "
+ invitedBy, Toast.LENGTH_SHORT).show();
}
}
- (void)conversation:(LCIMConversation *)conversation membersAdded:(NSArray *)clientIds byClientId:(NSString *)clientId {
NSLog(@"%@", [NSString stringWithFormat:@"%@ joined the conversation; operated by %@",[clientIds objectAtIndex:0],clientId]);
}
jerry.on(
Event.MEMBERS_JOINED,
function membersJoinedEventHandler(payload, conversation) {
console.log(payload.members, payload.invitedBy, conversation.id);
}
);
Leaving Conversations
With more and more people being invited by Tom, Jerry feels that he doesn't like most of them and wants to leave the conversation. He can do that with the following code:
- Unity
- Android
- iOS
- JavaScript
await conversation.Quit();
conversation.quit(new LCIMConversationCallback(){
@Override
public void done(LCIMException e){
if(e==null){
// You left the conversation
}
}
});
[conversation quitWithCallback:^(BOOL succeeded, NSError *error) {
if (succeeded) {
NSLog(@"You left the conversation!");
}
}];
conversation
.quit()
.then(function (conversation) {
console.log("You left the conversation!", conversation.members);
})
.catch(console.error.bind(console));
After leaving the conversation, Jerry will no longer receive messages from it. Here is the sequence diagram of the operation:
Other members can listen to MEMBERS_LEFT
to know that Jerry left the conversation:
- Unity
- Android
- iOS
- JavaScript
mary.OnMembersLeft = (conv, members, initBy) => {
WriteLine($"{members} left {conv.Id}; operated by {initBy}");
}
public class CustomConversationEventHandler extends LCIMConversationEventHandler {
@Override
public void onMemberLeft(LCIMClient client, LCIMConversation conversation, List<String> members,
String kickedBy) {
// Things to do after someone left
}
}
// If Mary is logged in, the following callback will be triggered when Jerry leaves the conversation
- (void)conversation:(LCIMConversation *)conversation membersRemoved:(NSArray *)clientIds byClientId:(NSString *)clientId {
NSLog(@"%@", [NSString stringWithFormat:@"%@ 离开了对话,操作者为:%@",[clientIds objectAtIndex:0],clientId]);
}
mary.on(
Event.MEMBERS_LEFT,
function membersLeftEventHandler(payload, conversation) {
console.log(payload.members, payload.kickedBy, conversation.id);
}
);
Summary of Event Notifications Regarding Changes of Members
The sequence diagrams displayed earlier already described what would happen when certain events are triggered. The table below serves as a summary of them.
Assuming that Tom and Jerry are already in the conversation:
- Unity
- Android
- iOS
- JavaScript
Operation | Tom | Jerry | Mary | William |
---|---|---|---|---|
Tom invites Mary | OnMembersJoined | OnMembersJoined | OnInvited | / |
Tom removes Mary | OnMembersLeft | OnMembersLeft | OnKicked | / |
William joins | OnMembersJoined | OnMembersJoined | / | OnMembersJoined |
Jerry leaves | OnMembersLeft | OnMembersLeft | / | OnMembersLeft |
Operation | Tom | Jerry | Mary | William |
---|---|---|---|---|
Tom invites Mary | onMemberJoined | onMemberJoined | onInvited | / |
Tom removes Mary | onMemberLeft | onMemberLeft | onKicked | / |
William joins | onMemberJoined | onMemberJoined | / | onMemberJoined |
Jerry leaves | onMemberLeft | onMemberLeft | / | onMemberLeft |
Operation | Tom | Jerry | Mary | William |
---|---|---|---|---|
Tom invites Mary | membersAdded | membersAdded | invitedByClientId | / |
Tom removes Mary | membersRemoved | membersRemoved | kickedByClientId | / |
William joins | membersAdded | membersAdded | / | membersAdded |
Jerry leaves | membersRemoved | kickedByClientId | / | membersRemoved |
Rich Media Messages
We've seen how we can send messages containing plain text. Now let's see how we can send rich media messages like images, videos, and locations.
The Instant Messaging service provides out-of-the-box support for messages containing text, files, images, audios, videos, locations, and binary data. All of them, except binary data, are sent as strings, though there are some slight differences between text messages and rich media messages (files, images, audios, and videos):
- When sending text messages, the messages themselves are sent directly as strings.
- When sending rich media messages (like images), the SDK will first upload the binary files to the cloud with the Data Storage service's
AVFile
interface, then embed the URLs of them into the messages being sent. We can say that the essence of an image message is a text message holding the URL of the image.
Files stored on the Data Storage service have CDN enabled by default. Therefore, binary data (like images) are not directly encoded as part of text messages. This helps users access them faster and the cost on you can be lowered as well.
Default Message Types
The following message types are offered by default:
TextMessage
Text messageImageMessage
Image messageAudioMessage
Audio messageVideoMessage
Video messageFileMessage
File message (.txt, .doc, .md, etc.)LocationMessage
Location message
All of them are derived from LCIMMessage
, with the following properties available for each:
- Unity
- Android
- iOS
- JavaScript
Name | Type | Description |
---|---|---|
content | String | The content of the message. |
clientId | String | The clientId of the sender. |
conversationId | String | The ID of the conversation. |
messageId | String | A unique ID for each message. Assigned by the cloud automatically. |
timestamp | long | The time the message is sent. Assigned by the cloud automatically. |
receiptTimestamp | long | The time the message is delivered. Assigned by the cloud automatically. |
status | A member of AVIMMessageStatus | The status of the message. Could be one of:AVIMMessageStatusNone (unknown)AVIMMessageStatusSending (sending)AVIMMessageStatusSent (sent)AVIMMessageStatusReceipt (delivered)AVIMMessageStatusFailed (failed) |
ioType | A member of AVIMMessageIOType | The direction of the message. Could be one of:AVIMMessageIOTypeIn (sent to the current user)AVIMMessageIOTypeOut (sent by the current user) |
Name | Type | Description |
---|---|---|
content | String | The content of the message. |
clientId | String | The clientId of the sender. |
conversationId | String | The ID of the conversation. |
messageId | String | A unique ID for each message. Assigned by the cloud automatically. |
timestamp | long | The time the message is sent. Assigned by the cloud automatically. |
receiptTimestamp | long | The time the message is delivered. Assigned by the cloud automatically. |
status | A member of MessageStatus | The status of the message. Could be one of:StatusNone (unknown)StatusSending (sending)StatusSent (sent)StatusReceipt (delivered)StatusFailed (failed) |
ioType | A member of MessageIOType | The direction of the message. Could be one of:TypeIn (sent to the current user)TypeOut (sent by the current user) |
Name | Type | Description |
---|---|---|
content | NSString | The content of the message. |
clientId | NSString | The clientId of the sender. |
conversationId | NSString | The ID of the conversation. |
messageId | NSString | A unique ID for each message. Assigned by the cloud automatically. |
sendTimestamp | int64_t | The time the message is sent. Assigned by the cloud automatically. |
deliveredTimestamp | int64_t | The time the message is delivered. Assigned by the cloud automatically. |
status | A member of AVIMMessageStatus | The status of the message. Could be one of:LCIMMessageStatusNone (unknown)LCIMMessageStatusSending (sending)LCIMMessageStatusSent (sent)LCIMMessageStatusDelivered (delivered)LCIMMessageStatusFailed (failed) |
ioType | A member of LCIMMessageIOType | The direction of the message. Could be one of:LCIMMessageIOTypeIn (sent to the current user)LCIMMessageIOTypeOut (sent by the current user) |
Name | Type | Description |
---|---|---|
from | String | The clientId of the sender. |
cid | String | The ID of the conversation. |
id | String | A unique ID for each message. Assigned by the cloud automatically. |
timestamp | Date | The time the message is sent. Assigned by the cloud automatically. |
deliveredAt | Date | The time the message is delivered. |
status | Symbol | The status of the message. Could be one of the members of MessageStatus :MessageStatus.NONE (unknown)MessageStatus.SENDING (sending)MessageStatus.SENT (sent)MessageStatus.DELIVERED (delivered)MessageStatus.FAILED (failed) |
A number is assigned to each message type which can be used by your app to identify it. Negative numbers are for those defined by the SDK (see the table below) and positive ones are for your own types. 0
is reserved for untyped messages.
Message Type | Number |
---|---|
Text messages | -1 |
Image messages | -2 |
Audio messages | -3 |
Video messages | -4 |
Location messages | -5 |
File messages | -6 |
Image Messages
Sending Image Files
An image message can be constructed from either binary data or a local path. The diagram below shows the sequence of it:
Notes:
- The "Local" in the diagram could be
localStorage
orcamera
, meaning that the image could be either from the local storage of the phone (like iPhone's Photo Library) or taken in real time with the camera API. LCFile
is the file object used by the Data Storage service.
The diagram above may look complicated, but the code itself is quite simple since the image gets automatically uploaded when being sent with the send
method:
- Unity
- Android
- iOS
- JavaScript
var image = new LCFile("screenshot.png", new Uri("http://example.com/screenshot.png"));
var imageMessage = new LCIMImageMessage(image);
imageMessage.Text = "Sent from Windows";
await conversation.Send(imageMessage);
LCFile file = LCFile.withAbsoluteLocalPath("San_Francisco.png", Environment.getExternalStorageDirectory() + "/San_Francisco.png");
// Create an image message
LCIMImageMessage m = new LCIMImageMessage(file);
m.setText("Sent from Android");
conv.sendMessage(m, new LCIMConversationCallback() {
@Override
public void done(LCIMException e) {
if (e == null) {
// Sent
}
}
});
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *imagePath = [documentsDirectory stringByAppendingPathComponent:@"Tarara.png"];
NSError *error;
LCFile *file = [LCFile fileWithLocalPath:imagePath error:&error];
LCIMImageMessage *message = [LCIMImageMessage messageWithText:@"Tarara looks sweet." file:file attributes:nil];
[conversation sendMessage:message callback:^(BOOL succeeded, NSError *error) {
if (succeeded) {
NSLog(@"Sent!");
}
}];
// ImageMessage and other rich media messages depend on the Data Storage service and the rich media message plugin.
// Refer to the SDK setup guide for details on how to import and initialize the SDKs.
var fileUploadControl = $("#photoFileUpload")[0];
var file = new AV.File("avatar.jpg", fileUploadControl.files[0]);
file
.save()
.then(function () {
var message = new ImageMessage(file);
message.setText("Sent from Ins");
message.setAttributes({ location: "San Francisco" });
return conversation.send(message);
})
.then(function () {
console.log("Sent!");
})
.catch(console.error.bind(console));
Sending Image URLs
Besides sending an image directly, a user may also copy the URL of an image from somewhere else and send it to a conversation:
- Unity
- Android
- iOS
- JavaScript
var image = new LCFile("girl.gif", new Uri("http://example.com/girl.gif"));
var imageMessage = new LCIMImageMessage(image);
imageMessage.Text = "Sent from Windows";
await conversation.Send(imageMessage);
LCFile file = new LCFile("girl","http://ww3.sinaimg.cn/bmiddle/596b0666gw1ed70eavm5tg20bq06m7wi.gif", null);
LCIMImageMessage m = new LCIMImageMessage(file);
m.setText("She looks sweet.");
// Create an image message
conv.sendMessage(m, new LCIMConversationCallback() {
@Override
public void done(LCIMException e) {
if (e == null) {
// Sent
}
}
});
// Tom sends an image to Jerry
LCFile *file = [LCFile fileWithURL:[self @"http://ww3.sinaimg.cn/bmiddle/596b0666gw1ed70eavm5tg20bq06m7wi.gif"]];
LCIMImageMessage *message = [LCIMImageMessage messageWithText:@"girl" file:file attributes:nil];
[conversation sendMessage:message callback:^(BOOL succeeded, NSError *error) {
if (succeeded) {
NSLog(@"Sent!");
}
}];
var AV = require("leancloud-storage");
var { ImageMessage } = initPlugin(AV, IM);
// Create an image message from URL
var file = new AV.File.withURL(
"girl",
"http://pic2.zhimg.com/6c10e6053c739ed0ce676a0aff15cf1c.gif"
);
file
.save()
.then(function () {
var message = new ImageMessage(file);
message.setText("She looks sweet.");
return conversation.send(message);
})
.then(function () {
console.log("Sent!");
})
.catch(console.error.bind(console));
Receiving Image Messages
The way to receive image messages is similar to that of basic messages. The only thing that needs to be added is to have the callback function retrieve the image and render it on the UI. For example:
- Unity
- Android
- iOS
- JavaScript
client.OnMessage = (conv, msg) => {
if (e.Message is LCIMImageMessage imageMessage) {
WriteLine(imageMessage.Url);
}
}
LCIMMessageManager.registerMessageHandler(LCIMImageMessage.class,
new LCIMTypedMessageHandler<LCIMImageMessage>() {
@Override
public void onMessage(LCIMImageMessage msg, LCIMConversation conv, LCIMClient client) {
// Only handle messages from Jerry
// sent to the conversation with conversationId 55117292e4b065f7ee9edd29
if ("Jerry".equals(client.getClientId()) && "55117292e4b065f7ee9edd29".equals(conv.getConversationId())) {
String fromClientId = msg.getFrom();
String messageId = msg.getMessageId();
String url = msg.getFileUrl();
Map<String, Object> metaData = msg.getFileMetaData();
if (metaData.containsKey("size")) {
int size = (Integer) metaData.get("size");
}
if (metaData.containsKey("width")) {
int width = (Integer) metaData.get("width");
}
if (metaData.containsKey("height")) {
int height = (Integer) metaData.get("height");
}
if (metaData.containsKey("format")) {
String format = (String) metaData.get("format");
}
}
}
});
- (void)conversation:(LCIMConversation *)conversation didReceiveTypedMessage:(LCIMTypedMessage *)message {
LCIMImageMessage *imageMessage = (LCIMImageMessage *)message;
// The ID of the message
NSString *messageId = imageMessage.messageId;
// The URL of the image file
NSString *imageUrl = imageMessage.file.url;
// The clientId of the sender
NSString *fromClientId = message.clientId;
}
var { Event, TextMessage } = require('leancloud-realtime');
var { ImageMessage } = initPlugin(AV, IM);
client.on(Event.MESSAGE, function messageEventHandler(message, conversation) {
var file;
switch (message.type) {
case ImageMessage.TYPE:
file = message.getFile();
console.log('Image received. URL: ' + file.url());
break;
}
}
Sending Audios, Videos, and Files
The Workflow
The SDK follows the steps below to send images, audios, videos, and files:
When constructing a file from a data stream using the client API:
- Construct an
LCFile
locally - Upload the
LCFile
to the cloud and retrieve itsmetaData
- Embed the
objectId
, URL, and metadata of theLCFile
into the message - Send the message
When constructing a file with an external URL:
- Embed the URL into the message without the metadata (like the length of audio) or
objectId
- Send the message
For example, when sending an audio message, the basic workflow would be: read the audio file (or record a new one) > construct an audio message > send the message.
- Unity
- Android
- iOS
- JavaScript
var audio = new LCFile("never-gonna-give-you-up.mp3", Path.Combine(Application.persistentDataPath, "never-gonna-give-you-up.mp3"));
var audioMessage = new LCIMAudioMessage(audio);
audioMessage.Text = "Check this out!";
await conversation.Send(audioMessage);
LCFile file = LCFile.withAbsoluteLocalPath("never-gonna-give-you-up.mp3",localFilePath);
LCIMAudioMessage m = new LCIMAudioMessage(file);
m.setText("Check this out!");
// Create an audio message
conv.sendMessage(m, new LCIMConversationCallback() {
@Override
public void done(LCIMException e) {
if (e == null) {
// Sent
}
}
});
NSError *error = nil;
LCFile *file = [LCFile fileWithLocalPath:localPath error:&error];
if (!error) {
LCIMAudioMessage *message = [LCIMAudioMessage messageWithText:@"Check this out!" file:file attributes:nil];
[conversation sendMessage:message callback:^(BOOL succeeded, NSError *error) {
if (succeeded) {
NSLog(@"Sent!");
}
}];
}
var AV = require("leancloud-storage");
var { AudioMessage } = initPlugin(AV, IM);
var fileUploadControl = $("#musicFileUpload")[0];
var file = new AV.File(
"never-gonna-give-you-up.mp3",
fileUploadControl.files[0]
);
file
.save()
.then(function () {
var message = new AudioMessage(file);
message.setText("Check this out!");
return conversation.send(message);
})
.then(function () {
console.log("Sent!");
})
.catch(console.error.bind(console));
Similar to image messages, you can construct audio messages from URLs as well:
- Unity
- Android
- iOS
- JavaScript
var audio = new LCFile("apple.aac", new Uri("https://some.website.com/apple.aac"));
var audioMessage = new LCIMAudioMessage(audio);
audioMessage.Text = "Here is the recording from Apple Special Event.";
await conversation.Send(audioMessage);
LCFile file = new LCFile("apple.aac", "https://some.website.com/apple.aac", null);
LCIMAudioMessage m = new LCIMAudioMessage(file);
m.setText("Here is the recording from Apple Special Event.");
conv.sendMessage(m, new LCIMConversationCallback() {
@Override
public void done(LCIMException e) {
if (e == null) {
// Sent
}
}
});
LCFile *file = [LCFile fileWithRemoteURL:[NSURL URLWithString:@"https://some.website.com/apple.aac"]];
LCIMAudioMessage *message = [LCIMAudioMessage messageWithText:@"Here is the recording from Apple Special Event." file:file attributes:nil];
[conversation sendMessage:message callback:^(BOOL succeeded, NSError *error) {
if (succeeded) {
NSLog(@"Sent!");
}
}];
var AV = require("leancloud-storage");
var { AudioMessage } = initPlugin(AV, IM);
var file = new AV.File.withURL(
"apple.aac",
"https://some.website.com/apple.aac"
);
file
.save()
.then(function () {
var message = new AudioMessage(file);
message.setText("Here is the recording from Apple Special Event.");
return conversation.send(message);
})
.then(function () {
console.log("Sent!");
})
.catch(console.error.bind(console));
Sending Location Messages
The code below sends a message containing a location:
- Unity
- Android
- iOS
- JavaScript
var location = new LCGeoPoint(31.3753285, 120.9664658);
var locationMessage = new LCIMLocationMessage(location);
await conversation.Send(locationMessage);
final LCIMLocationMessage locationMessage = new LCIMLocationMessage();
// The location here is hardcoded for demonstration; you can get actual locations with the API offered by the device
locationMessage.setLocation(new LCGeoPoint(31.3753285,120.9664658));
locationMessage.setText("Here is the location of the bakery.");
conversation.sendMessage(locationMessage, new LCIMConversationCallback() {
@Override
public void done(LCIMException e) {
if (null != e) {
e.printStackTrace();
} else {
// Sent
}
}
});
LCIMLocationMessage *message = [LCIMLocationMessage messageWithText:@"Here is the location of the bakery." latitude:31.3753285 longitude:120.9664658 attributes:nil];
[conversation sendMessage:message callback:^(BOOL succeeded, NSError *error) {
if (succeeded) {
NSLog(@"Sent!");
}
}];
var AV = require("leancloud-storage");
var { LocationMessage } = initPlugin(AV, IM);
var location = new AV.GeoPoint(31.3753285, 120.9664658);
var message = new LocationMessage(location);
message.setText("Here is the location of the bakery.");
conversation
.send(message)
.then(function () {
console.log("Sent!");
})
.catch(console.error.bind(console));
Back to Receiving Messages
- Unity
- Android
- iOS
- JavaScript
The C# SDK handles new messages with OnMessage
event callbacks:
jerry.OnMessage = (conv, msg) => {
if (msg is LCIMImageMessage imageMessage) {
} else if (msg is LCIMAudioMessage audioMessage) {
} else if (msg is LCIMVideoMessage videoMessage) {
} else if (msg is LCIMFileMessage fileMessage) {
} else if (msg is AVIMLocationMessage locationMessage) {
} else if (msg is InputtingMessage) {
WriteLine($"Custom message received: {inputtingMessage.TextContent} {inputtingMessage.Ecode}");
}
}
The Java/Android SDK handles new messages with LCIMMessageHandler
. You can register your own message handlers by calling LCIMMessageManager.registerDefaultMessageHandler
. LCIMMessageManager
offers two different methods for you to register default message handlers and handlers for specific message types:
/**
* Register default message handler.
*
* @param handler
*/
public static void registerDefaultMessageHandler(LCIMMessageHandler handler);
/**
* Register handler for specific message type.
*
* @param clazz The message type
* @param handler
*/
public static void registerMessageHandler(Class<? extends LCIMMessage> clazz, MessageHandler<?> handler);
/**
* Deregister handler for specific message type.
*
* @param clazz
* @param handler
*/
public static void unregisterMessageHandler(Class<? extends LCIMMessage> clazz, MessageHandler<?> handler);
Different handlers can be registered or deregistered for different message types (including those defined by yourself). These handlers should be set up when initializing the app.
If you call registerDefaultMessageHandler
on LCIMMessageManager
multiple times, only the last one would take effect. However, if you register LCIMMessageHandler
through registerMessageHandler
, different handlers could coexist with each other.
When a message is received by the client, the SDK would:
- Detect the type of the message, look for all the handlers registered for this type, and call the
onMessage
functions within all these handlers. - If no handler is found for this type,
defaultHandler
will be triggered.
So when handlers are specified for AVIMTypedMessage
(and its subtypes) and a global defaultHandler
is also specified, if the sender sends a general LCIMMessage
message, the receiver will have its handler in LCIMMessageManager.registerDefaultMessageHandler()
triggered; if the sender sends a message of LCIMTypedMessage
(or its subtype), the receiver will have its handler in LCIMMessageManager#registerMessageHandler()
triggered.
// 1. Register the default handler, which will only be invoked when none of the other handlers are invoked
LCIMMessageManager.registerDefaultMessageHandler(new LCIMMessageHandler(){
public void onMessage(LCIMMessage message, LCIMConversation conversation, LCIMClient client) {
// Receive the message
}
public void onMessageReceipt(LCIMMessage message, LCIMConversation conversation, LCIMClient client) {
// Your application may add new custom message types in the future. The SDK may add new built-in message types as well.
// Therefore, do not forget to handle them here. For example, you can notify the user to upgrade to a new version.
}
});
// 2. Register a handler for each type of message
LCIMMessageManager.registerMessageHandler(LCIMTypedMessage.class, new LCIMTypedMessageHandler<LCIMTypedMessage>(){
public void onMessage(LCIMTypedMessage message, LCIMConversation conversation, LCIMClient client) {
switch (message.getMessageType()) {
case LCIMMessageType.TEXT_MESSAGE_TYPE:
// Do something
LCIMTextMessage textMessage = (LCIMTextMessage)message;
break;
case LCIMMessageType.IMAGE_MESSAGE_TYPE:
// Do something
LCIMImageMessage imageMessage = (LCIMImageMessage)message;
break;
case LCIMMessageType.AUDIO_MESSAGE_TYPE:
// Do something
LCIMAudioMessage audioMessage = (LCIMAudioMessage)message;
break;
case LCIMMessageType.VIDEO_MESSAGE_TYPE:
// Do something
LCIMVideoMessage videoMessage = (LCIMVideoMessage)message;
break;
case LCIMMessageType.LOCATION_MESSAGE_TYPE:
// Do something
LCIMLocationMessage locationMessage = (LCIMLocationMessage)message;
break;
case LCIMMessageType.FILE_MESSAGE_TYPE:
// Do something
LCIMFileMessage fileMessage = (LCIMFileMessage)message;
break;
case LCIMMessageType.RECALLED_MESSAGE_TYPE:
// Do something
LCIMRecalledMessage recalledMessage = (LCIMRecalledMessage)message;
break;
case 123:
// This is a custom message type
// Do something
CustomMessage customMessage = (CustomMessage)message;
break;
}
}
public void onMessageReceipt(LCIMTypedMessage message, LCIMConversation conversation, LCIMClient client) {
// Do something after receiving the message
}
});
The Objective-C SDK handles new messages with LCIMClientDelegate
and uses two separate methods to handle basic messages (LCIMMessage
) and rich media messages (LCIMTypedMessage
; including messages with custom types):
/*!
New basic message received.
@param conversation - The conversation.
@param message - The content of the message.
*/
- (void)conversation:(LCIMConversation *)conversation didReceiveCommonMessage:(LCIMMessage *)message;
/*!
New rich media message received.
@param conversation - The conversation.
@param message - The content of the message.
*/
- (void)conversation:(LCIMConversation *)conversation didReceiveTypedMessage:(LCIMTypedMessage *)message;
// Handle messages with built-in types
- (void)conversation:(LCIMConversation *)conversation didReceiveTypedMessage:(LCIMTypedMessage *)message {
if (message.mediaType == LCIMMessageMediaTypeImage) {
LCIMImageMessage *imageMessage = (LCIMImageMessage *)message; // Handle image message
} else if(message.mediaType == LCIMMessageMediaTypeAudio){
// Handle audio message
} else if(message.mediaType == LCIMMessageMediaTypeVideo){
// Handle video message
} else if(message.mediaType == LCIMMessageMediaTypeLocation){
// Handle location message
} else if(message.mediaType == LCIMMessageMediaTypeFile){
// Handle file message
} else if(message.mediaType == LCIMMessageMediaTypeText){
// Handle text message
} else if(message.mediaType == 123){
// Handle custom message type
}
}
// Handle unknown messages types
- (void)conversation:(LCIMConversation *)conversation didReceiveCommonMessage:(LCIMMessage *)message {
// Your application may add new custom message types in the future. The SDK may add new built-in message types as well.
// Therefore, do not forget to handle them here. For example, you can notify the user to upgrade to a new version.
}
When a new message comes in, the JavaScript SDK would always trigger the callback set for the event Event.MESSAGE
on IMClient
regardless of the type of the message. You can address different types of messages in different ways within the callback function.
// Load TypedMessagesPlugin when initializing Realtime
var { Event, TextMessage } = require("leancloud-realtime");
var { FileMessage, ImageMessage, AudioMessage, VideoMessage, LocationMessage } =
initPlugin(AV, IM);
// Register handler for the MESSAGE event
client.on(Event.MESSAGE, function messageEventHandler(message, conversation) {
// Your logic here
var file;
switch (message.type) {
case TextMessage.TYPE:
console.log(
"Text message received. Text: " +
message.getText() +
", ID: " +
message.id
);
break;
case FileMessage.TYPE:
file = message.getFile(); // file is an AV.File instance
console.log(
"File message received. URL: " +
file.url() +
", Size: " +
file.metaData("size")
);
break;
case ImageMessage.TYPE:
file = message.getFile();
console.log(
"Image message received. URL: " +
file.url() +
", Width: " +
file.metaData("width")
);
break;
case AudioMessage.TYPE:
file = message.getFile();
console.log(
"Audio message received. URL: " +
file.url() +
", Duration: " +
file.metaData("duration")
);
break;
case VideoMessage.TYPE:
file = message.getFile();
console.log(
"Video message received. URL: " +
file.url() +
", Duration: " +
file.metaData("duration")
);
break;
case LocationMessage.TYPE:
var location = message.getLocation();
console.log(
"Location message received. Latitude: " +
location.latitude +
", Longitude: " +
location.longitude
);
break;
case 1:
console.log("OperationMessage is the custom message type");
default:
// Your application may add new custom message types in the future. The SDK may add new built-in message types as well.
// Therefore, do not forget to handle them here. For example, you can notify the user to upgrade to a new version.
console.warn("收到未知类型消息");
}
});
// `MESSAGE` event will be triggered on conversation as well
conversation.on(Event.MESSAGE, function messageEventHandler(message) {
// Your logic here
});
The code above involves the reception of messages with custom types. We will cover more about it in the second chapter.
Custom Attributes
A Conversation
object holds some built-in properties which match the fields in the _Conversation
table. The table below shows these built-in properties:
- Unity
- Android
- iOS
- JavaScript
Property of AVIMConversation | Field in _Conversation | Description |
---|---|---|
CurrentClient | N/A | The AVIMClient the conversation belongs to. |
ConversationId | objectId | A globally unique ID. |
Name | name | The name of the conversation. Shared by all members. |
MemberIds | m | The list of members. |
MuteMemberIds | mu | The list of members that muted the conversation. |
Creator | c | The creator of the conversation. |
IsTransient | tr | Whether it is a chat room. |
IsSystem | sys | Whether it is a system conversation. |
IsUnique | unique | If this is true , the same conversation will be reused when a new conversation is created with the same composition of members and unique to be true . |
IsTemporary | N/A | Whether it is a temporary conversation that will not be saved to the _Conversation class. |
CreatedAt | createdAt | The time the conversation is created. |
UpdatedAt | updatedAt | The time the conversation is updated. |
LastMessageAt | lm | The time the last message is sent. |
Getter of LCIMConversation | Field in _Conversation | Description |
---|---|---|
getAttributes | attr | Custom attributes. |
getConversationId | objectId | A globally unique ID. |
getCreatedAt | createdAt | The time the conversation is created. |
getCreator | c | The creator of the conversation. |
getLastDeliveredAt | N/A | The time the last message being delivered is sent (for one-on-one chats only). |
getLastMessage | N/A | The last message. Could be empty. |
getLastMessageAt | lm | The time the last message is sent. |
getLastReadAt | N/A | The time the last message being read is sent (for one-on-one chats only). |
getMembers | m | The list of members. |
getName | name | The name of the conversation. Shared by all members. |
getTemporaryExpiredat | N/A | Time to live (applicable for temporary conversations only). |
getUniqueId | uniqueId | A globally unique ID for Unique Conversation . |
getUnreadMessagesCount | N/A | The number of unread messages. |
getUpdatedAt | updatedAt | The time the conversation is updated. |
isSystem | sys | Whether it is a system conversation. |
isTemporary | N/A | Whether it is a temporary conversation that will not be saved to the _Conversation class. |
isTransient | tr | Whether it is a chat room. |
isUnique | unique | Whether it is a Unique Conversation . |
unreadMessagesMentioned | N/A | Whether unread messages contain a mention of the current Client . |
Property of LCIMConversation | Field in _Conversation | Description |
---|---|---|
clientID | N/A | The ID of the Client the conversation belongs to. |
conversationId | objectId | A globally unique ID. |
creator | c | The creator of the conversation. |
createdAt | createdAt | The time the conversation is created. |
updatedAt | updatedAt | The time the conversation is updated. |
lastMessage | N/A | The last message. Could be empty. |
lastMessageAt | lm | The time the last message is sent. |
lastReadAt | N/A | The time the last message being read is sent (for one-on-one chats only). |
lastDeliveredAt | N/A | The time the last message being delivered is sent (for one-on-one chats only). |
unreadMessagesCount | N/A | The number of unread messages. |
unreadMessageContainMention | N/A | Whether unread messages contain a mention of the current Client . |
name | name | The name of the conversation. Shared by all members. |
members | m | The list of members. |
attributes | attr | Custom attributes. |
uniqueId | uniqueId | A globally unique ID for Unique Conversation . |
unique | unique | Whether it is a Unique Conversation . |
transient | tr | Whether it is a chat room. |
system | sys | Whether it is a system conversation. |
temporary | N/A | Whether it is a temporary conversation that will not be saved to the _Conversation class. |
temporaryTTL | N/A | Time to live (applicable for temporary conversations only). |
muted | N/A | Whether the current user muted the conversation. |
imClient | N/A | The LCIMClient the conversation belongs to. |
Property of Conversation | Field in _Conversation | Description |
---|---|---|
createdAt | createdAt | The time the conversation is created. |
creator | c | The creator of the conversation. |
id | objectId | A globally unique ID. |
lastDeliveredAt | N/A | The time the last message being delivered is sent (for one-on-one chats only). |
lastMessage | N/A | The last message. Could be empty. |
lastMessageAt | lm | The time the last message is sent. |
lastReadAt | N/A | The time the last message being read is sent (for one-on-one chats only). |
members | m | The list of members. |
muted | N/A | Whether the current user muted the conversation. |
mutedMembers | mu | The list of members that muted the conversation. |
name | name | The name of the conversation. Shared by all members. |
system | sys | Whether it is a system conversation. |
transient | tr | Whether it is a chat room. |
unreadMessagesCount | N/A | The number of unread messages. |
updatedAt | updatedAt | The time the conversation is updated. |
However, direct write operations on the _Conversation
table are frowned upon:
- The conversation queries sent by client-side SDKs in websocket connections will first reach the Instant Messaging server's in-memory cache. Direct write operations on the
_Conversation
table will not update the cache, which may cause cache inconsistency. - With direct write operations on the
_Conversation
table, the Instant Messaging server has no chance to notify the client-side. Thus the client-side will not receive any corresponding events. - If Instant Messaging hooks are defined, direct write operations on the
_Conversation
table will not trigger them.
For administrative tasks, the dedicated Instant Messaging REST API interface is recommended.
Besides these built-in properties, you can also define your custom attributes to store more data with each conversation.
Creating Custom Attributes
When introducing one-on-one conversations, we mentioned that IMClient#createConversation
allows you to attach custom attributes to a conversation. Now let's see how we can do that.
Assume that we need to add two properties { "type": "private", "pinned": true }
to a conversation we are creating. We can do so by passing in the properties when calling IMClient#createConversation
:
- Unity
- Android
- iOS
- JavaScript
var properties = new Dictionary<string, object> {
{ "type", "private" },
{ "pinned", true }
};
var conversation = await tom.CreateConversation("Jerry", name: "Tom & Jerry", unique: true, properties: properties);
HashMap<String,Object> attr = new HashMap<String,Object>();
attr.put("type","private");
attr.put("pinned",true);
client.createConversation(Arrays.asList("Jerry"),"Tom & Jerry", attr, false, true,
new LCIMConversationCreatedCallback(){
@Override
public void done(LCIMConversation conv,LCIMException e){
if(e==null){
// Conversation created
}
}
});
// Tom creates a conversation named "Tom & Jerry" and attaches custom attributes to it
LCIMConversationCreationOption *option = [LCIMConversationCreationOption new];
option.name = @"Tom & Jerry";
option.attributes = @{
@"type": @"private",
@"pinned": @(YES)
};
[self createConversationWithClientIds:@[@"Jerry"] option:option callback:^(LCIMConversation * _Nullable conversation, NSError * _Nullable error) {
if (succeeded) {
NSLog(@"Conversation created!");
}
}];
tom
.createConversation({
members: ["Jerry"],
name: "Tom & Jerry",
unique: true,
type: "private",
pinned: true,
})
.then(function (conversation) {
console.log("Conversation created! ID: " + conversation.id);
})
.catch(console.error.bind(console));
The SDK allows everyone in a conversation to access its custom attributes. You can even query conversations that satisfy certain custom attributes. See Querying Conversations with Custom Conditions.
Updating and Retrieving Properties
The built-in properties (like name
) of a Conversation
object can be updated by all the members unless you set restrictions in your app:
- Unity
- Android
- iOS
- JavaScript
await conversation.UpdateInfo(new Dictionary<string, object> {
{ "name", "Tom is Smart" }
});
LCIMConversation conversation = client.getConversation("55117292e4b065f7ee9edd29");
conversation.setName("Tom is Smart");
conversation.updateInfoInBackground(new LCIMConversationCallback(){
@Override
public void done(LCIMException e){
if(e==null){
// Updated
}
}
});
conversation[@"name"] = @"Tom is Smart";
[conversation updateWithCallback:^(BOOL succeeded, NSError * _Nullable error) {
if (succeeded) {
NSLog(@"Updated!");
}
}];
conversation.name = "Tom is Smart";
conversation.save();
Custom attributes can also be retrieved or updated by all the members:
- Unity
- Android
- iOS
- JavaScript
// Retrieve custom attribute
var type = conversation["type"];
// Set new value for pinned
await conversation.UpdateInfo(new Dictionary<string, object> {
{ "pinned", false }
});
// Retrieve custom attribute
String type = conversation.get("attr.type");
// Set new value for pinned
conversation.set("attr.pinned",false);
// Save
conversation.updateInfoInBackground(new LCIMConversationCallback(){
@Override
public void done(LCIMException e){
if(e==null){
// Saved
}
}
});
// Retrieve custom attribute
NSString *type = conversation.attributes[@"type"];
// Set new value for pinned
[conversation setObject:@(NO) forKey:@"attr.pinned"];
// Save
[conversation updateWithCallback:^(BOOL succeeded, NSError *error) {
if (succeeded) {
NSLog(@"Saved!");
}
}];
// Retrieve custom attribute
var type = conversation.get("attr.type");
// Set new value for pinned
conversation.set("attr.pinned", false);
// Save
conversation.save();
Notes on custom attributes:
The custom attributes specified with IMClient#createConversation
will be stored in the field attr
of the _Conversation
table. If you need to retrieve or update them later, the full path needs to be specified, like attr.type
.
Synchronization of Properties
The properties of a conversation (like name) are shared by everyone in it. If someone ever changes a property, other members need to get updated on it. In the example we used earlier, a user changed the name of a conversation to "Tom is Smart". How would other members get to know about it?
Instant Messaging offers the mechanism that automatically delivers the change made by a user to a conversation to all the members in it (for those who are offline, they will receive updates once they get online):
- Unity
- Android
- iOS
- JavaScript
jerry.OnConversationInfoUpdated = (conv, attrs, initBy) => {
WriteLine($"Conversation ${conv.Id} updated.");
};
// The following definition exists in LCIMConversationEventHandler
/**
* The properties of a conversation are updated
*
* @param client
* @param conversation
* @param attr The properties being updated
* @param operator The ID of the operator
*/
public void onInfoChanged(LCIMClient client, LCIMConversation conversation, JSONObject attr,
String operator)
/// Notification for conversation's attribution updated.
/// @param conversation Updated conversation.
/// @param date Updated date.
/// @param clientId Client ID which do this update.
/// @param updatedData Updated data.
/// @param updatingData Updating data.
- (void)conversation:(LCIMConversation *)conversation didUpdateAt:(NSDate * _Nullable)date byClientId:(NSString * _Nullable)clientId updatedData:(NSDictionary * _Nullable)updatedData updatingData:(NSDictionary * _Nullable)updatingData;
/**
* The properties of a conversation are updated
* @event IMClient#CONVERSATION_INFO_UPDATED
* @param {Object} payload
* @param {Object} payload.attributes The properties being updated
* @param {String} payload.updatedBy The ID of the operator
*/
var { Event } = require("leancloud-realtime");
client.on(Event.CONVERSATION_INFO_UPDATED, function (payload) {});
Notes:
You can either retrieve the properties being updated from the callback function or directly read the latest values from the Conversation
object.
Retrieving Member Lists
To get the list of members in a conversation, we can call the method for fetching on a Conversation
object and then get the result from it:
- Unity
- Android
- iOS
- JavaScript
await conversation.Fetch();
// fetchInfoInBackground will trigger an operation to retrieve the latest data from the cloud
conversation.fetchInfoInBackground(new LCIMConversationCallback() {
@Override
public void done(LCIMException e) {
if (e == null) {
conversation.getMembers();
}
}
});
// fetchWithCallback will trigger an operation to retrieve the latest data from the cloud
[conversation fetchWithCallback:^(BOOL succeeded, NSError *error) {
if (succeeded) {
NSLog(@"", conversation.members);
}
}];
// fetch will trigger an operation to retrieve the latest data from the cloud
conversation.fetch().then(function(conversation) {
console.log('members: ', conversation.members);
).catch(console.error.bind(console));
Notes:
You can only get member lists of basic conversations. Chat rooms and system conversations don't have member lists.
Querying Conversations with Custom Conditions
There are more ways to get a Conversation
besides listening to incoming events. You might want your users to search chat rooms by the names or locations of them, or to look for conversations that have certain members in them. All these requirements can be satisfied with the help of queries.
Queries on ID
Here ID refers to the objectId
in the _Conversation
table. Since IDs are indexed, querying by ID is the easiest and most efficient way to look for a conversation:
- Unity
- Android
- iOS
- JavaScript
var query = tom.GetQuery();
var conversation = await query.Get("551260efe4b01608686c3e0f");
LCIMConversationsQuery query = tom.getConversationsQuery();
query.whereEqualTo("objectId","551260efe4b01608686c3e0f");
query.findInBackground(new LCIMConversationQueryCallback(){
@Override
public void done(List<LCIMConversation> convs,LCIMException e){
if(e==null){
if(convs!=null && !convs.isEmpty()){
// convs.get(0) is the conversation being found
}
}
}
});
LCIMConversationQuery *query = [tom conversationQuery];
[query getConversationById:@"551260efe4b01608686c3e0f" callback:^(LCIMConversation *conversation, NSError *error) {
if (succeeded) {
NSLog(@"Query completed!");
}
}];
tom
.getConversation("551260efe4b01608686c3e0f")
.then(function (conversation) {
console.log(conversation.id);
})
.catch(console.error.bind(console));
Querying by Conditions
There are a variety of ways for you to look for conversations that satisfy certain conditions.
Let's start with equalTo
which is the simplest method for querying conversations. The code below looks for all the conversations that have type
(a string field) to be private
:
- Unity
- Android
- iOS
- JavaScript
var query = tom.GetQuery()
.WhereEqualTo("type", "private");
await query.Find();
LCIMConversationsQuery query = tom.getConversationsQuery();
query.whereEqualTo("attr.type","private");
// Perform query
query.findInBackground(new LCIMConversationQueryCallback(){
@Override
public void done(List<LCIMConversation> convs,LCIMException e){
if(e == null){
// convs contains all the results
}
}
});
LCIMConversationQuery *query = [tom conversationQuery];
[query whereKey:@"attr.type" equalTo:@"private"];
// Perform query
[query findConversationsWithCallback:^(NSArray *objects, NSError *error) {
NSLog(@"找到 %ld 个对话!", [objects count]);
}];
var query = client.getQuery();
query.equalTo("attr.type", "private");
query
.find()
.then(function (conversations) {
// conversations contains all the results
})
.catch(console.error.bind(console));
The interface for querying conversations is very similar to that for querying objects in Data Storage. If you're already familiar with Data Storage, it shouldn't be hard for you to learn how to query conversations:
- You can get query results with
find
- You can get number of results with
count
- You can get the first conversation satisfying conditions with
first
- You can implement pagination with
skip
andlimit
You can also apply conditions like "greater than", "greater than or equal to", "less than", and "less than or equal to" to Number
and Date
fields:
- Unity
- Android
- iOS
- JavaScript
Logic | AVIMConversationQuery Method |
---|---|
Equal to | WhereEqualTo |
Not equal to | WhereNotEqualsTo |
Greater than | WhereGreaterThan |
Greater than or equal to | WhereGreaterThanOrEqualsTo |
Less than | WhereLessThan |
Less than or equal to | WhereLessThanOrEqualsTo |
Logic | LCIMConversationsQuery Method |
---|---|
Equal to | whereEqualTo |
Not equal to | whereNotEqualsTo |
Greater than | whereGreaterThan |
Greater than or equal to | whereGreaterThanOrEqualsTo |
Less than | whereLessThan |
Less than or equal to | whereLessThanOrEqualsTo |
Logic | LCIMConversationQuery Method |
---|---|
Equal to | equalTo |
Not equal to | notEqualTo |
Greater than | greaterThan |
Greater than or equal to | greaterThanOrEqualTo |
Less than | lessThan |
Less than or equal to | lessThanOrEqualTo |
Logic | Constraint of IMConversationQuery |
---|---|
Equal to | equalTo |
Not equal to | notEqualTo |
Greater than | greaterThan |
Greater than or equal to | greaterThanOrEqualTo |
Less than | lessThan |
Less than or equal to | lessThanOrEqualTo |
Notes on default query conditions:
When querying conversations, if there isn't any where
condition specified, ConversationQuery
will look for conversations containing the current user by default. Such a condition will be dismissed if any where
condition is applied to the query. If you want to look for conversations containing certain clientId
, you can follow the way introduced in Queries on Array Values to perform queries on m
with the value of clientId
. This won't cause any conflict with the default condition.
Using Regular Expressions
You can use regular expressions as conditions when querying with ConversationsQuery
. For example, to look for all the conversations that have language
to be a Chinese character:
- Unity
- Android
- iOS
- JavaScript
query.WhereMatches("language", "[\\u4e00-\\u9fa5]"); // language is a Chinese character
query.whereMatches("language","[\\u4e00-\\u9fa5]"); // language is a Chinese character
[query whereKey:@"language" matchesRegex:@"[\\u4e00-\\u9fa5]"]; // language is a Chinese character
query.matches("language", /[\\u4e00-\\u9fa5]/); // language is a Chinese character
Queries on String Values
You can look for conversations with string values that start with a particular string, which is similar to LIKE 'keyword%'
in SQL. For example, to look for all the conversations with names starting with education
:
- Unity
- Android
- iOS
- JavaScript
query.WhereStartsWith("name", "education");
query.whereStartsWith("name","education");
[query whereKey:@"name" hasPrefix:@"education"];
query.startsWith("name", "education");
You can also look for conversations with string values that include a particular string, which is similar to LIKE '%keyword%'
in SQL. For example, to look for all the conversations with names including education
:
- Unity
- Android
- iOS
- JavaScript
query.WhereContains("name", "education");
query.whereContains("name","education");
[query whereKey:@"name" containsString:@"education"];
query.contains("name", "education");
If you want to look for conversations with string values that exclude a particular string, you can use regular expressions. For example, to look for all the conversations with names excluding education
:
- Unity
- Android
- iOS
- JavaScript
query.WhereMatches("name", "^((?!education).)* $ ");
query.whereMatches("name","^((?!education).)* $ ");
[query whereKey:@"name" matchesRegex:@"^((?!education).)* $ "];
var regExp = new RegExp("^((?!education).)*$", "i");
query.matches("name", regExp);
Queries on Array Values
You can use containsAll
, containedIn
, and notContainedIn
to perform queries on array values. For example, to look for all the conversations containing Tom
:
- Unity
- Android
- iOS
- JavaScript
var members = new List<string> { "Tom" };
query.WhereContainedIn("m", members);
query.whereContainedIn("m", Arrays.asList("Tom"));
[query whereKey:@"m" containedIn:@[@"Tom"]];
query.containedIn("m", ["Tom"]);
Queries on Existence
You can look for conversations with or without certain fields to be empty. For example, to look for all the conversations with lm
to be empty:
- Unity
- Android
- iOS
- JavaScript
query.WhereDoesNotExist("lm");
query.whereDoesNotExist("lm");
[query whereKeyDoesNotExist:@"lm"];
query.doesNotExist("lm");
Or, to look for all the conversations with lm
not to be empty:
- Unity
- Android
- iOS
- JavaScript
query.WhereExists("lm");
query.whereExists("lm");
[query whereKeyExists:@"lm"];
query.exists("lm");
Compound Queries
To look for all the conversations with age
to be less than 18
and keywords
containing education
:
- Unity
- Android
- iOS
- JavaScript
query.WhereContains("keywords", "education")
.WhereLessThan("age", 18);
query.whereContains("keywords", "education");
query.whereLessThan("age", 18);
[query whereKey:@"keywords" containsString:@"education"];
[query whereKey:@"age" lessThan:@(18)];
query.contains("keywords", "education").lessThan("age", 18);
You can also combine two queries with and
or or
to form a new query.
For example, to look for all the conversations that either have age
to be less than 18
or have keywords
containing education
:
- Unity
- Android
- iOS
- JavaScript
// Not supported yet
LCIMConversationsQuery ageQuery = tom.getConversationsQuery();
ageQuery.whereLessThan('age', 18);
LCIMConversationsQuery keywordsQuery = tom.getConversationsQuery();
keywordsQuery.whereContains('keywords', 'education');
LCIMConversationsQuery query = LCIMConversationsQuery.or(Arrays.asList(priorityQuery, statusQuery));
LCIMConversationQuery *ageQuery = [tom conversationQuery];
[ageQuery whereKey:@"age" greaterThan:@(18)];
LCIMConversationQuery *keywordsQuery = [tom conversationQuery];
[keywordsQuery whereKey:@"keywords" containsString:@"education"];
LCIMConversationQuery *query = [LCIMConversationQuery orQueryWithSubqueries:[NSArray arrayWithObjects:ageQuery,keywordsQuery,nil]];
// Not supported yet
Sorting
You can sort the results of a query by ascending or descending order on certain fields. For example:
- Unity
- Android
- iOS
- JavaScript
query.OrderByDescending("createdAt");
query.orderByDescending("createdAt");
[query orderByDescending:@"createdAt"];
// Ascend by name and descend by creation time
query.addAscending("name").addDescending("createdAt");
Excluding Member Lists from Results
When searching conversations, you can exclude the lists of members from query results if you don't need them. By doing so, their members
fields will become empty arrays. This helps you improve the speed of your app and reduces the network traffic needed.
- Unity
- Android
- iOS
- JavaScript
query.Compact = true;
query.setCompact(true);
query.option = LCIMConversationQueryOptionCompact;
query.compact(true);
Including Latest Messages in Results
Many chatting apps show the latest message of each conversation in the conversation list. If you want a similar function in your app, you can turn on the option when querying conversations:
- Unity
- Android
- iOS
- JavaScript
query.WithLastMessageRefreshed = true;
query.setWithLastMessagesRefreshed(true);
query.option = LCIMConversationQueryOptionWithMessage;
// enable withLastMessagesRefreshed to include the latest message of each conversation in the results
query.withLastMessagesRefreshed(true);
Keep in mind that what this option really does is to refresh the latest messages of conversations. Due to the existence of the cache, it is still possible for you to retrieve the outdated "latest messages" even though you set the option to be false
.
Caching Results
- Unity
- Android
- iOS
- JavaScript
Not supported yet.
By caching query results locally, if the device is offline, or if the app is just opened and the request for synchronizing with the cloud is not completed yet, there could still be some data available. You can also reduce the data usage of the user by performing queries with the cloud only when the app is first opened and having subsequent queries completed with local cache first.
Keep in mind that query results will be fed with local cache first and will be synchronized with the cloud right after that. The expiration time for cache is 1 hour. You can configure cache with the following method provided by LCIMConversationsQuery
:
// Set caching policy for LCIMConversationsQuery
public void setQueryPolicy(LCQuery.CachePolicy policy);
If you want cache to be accessed only when there's an error querying with the cloud, you can do this:
LCIMConversationsQuery query = client.getConversationsQuery();
query.setQueryPolicy(LCQuery.CachePolicy.NETWORK_ELSE_CACHE);
query.findInBackground(new LCIMConversationQueryCallback() {
@Override
public void done(List<LCIMConversation> conversations, LCIMException e) {
}
});
By caching query results locally, if the device is offline, or if the app is just opened and the request for synchronizing with the cloud is not completed yet, there could still be some data available. You can also reduce the data usage of the user by performing queries with the cloud only when the app is first opened and having subsequent queries completed with local cache first.
Keep in mind that query results will be fed with local cache first and will be synchronized with the cloud right after that. The expiration time for cache is 1 hour. You can configure cache with the following method provided by LCIMConversationQuery
:
// Set caching policy; defaults to kLCCachePolicyCacheElseNetwork
@property (nonatomic) LCCachePolicy cachePolicy;
// Set expiration time; defaults to 1 hour (1 * 60 * 60)
@property (nonatomic) NSTimeInterval cacheMaxAge;
If you want cache to be accessed only when there's an error querying with the cloud, you can do this:
LCIMConversationQuery *query = [client conversationQuery];
query.cachePolicy = kLCCachePolicyNetworkElseCache;
[query findConversationsWithCallback:^(NSArray *objects, NSError *error) {
}];
See Data Storage Guide to learn more about the difference between all the caching policies.
Conversations will be cached in memory using dictionaries according to their IDs. Such cache will not be persisted.
Optimizing Performance
Since Conversation
objects are stored on Data Storage, you can make use of indexes to improve the efficiency of querying, just like how you would do to other classes. Here are some suggestions for optimizing performance:
- By default, indexes are created for
objectId
,updatedAt
, andcreatedAt
ofConversation
, so querying by these fields would be naturally fast. - Although it's possible to implement pagination with
skip
andlimit
, the speed would slow down when the dataset grows larger. It would be more efficient to make use ofupdatedAt
orlastMessageAt
instead. - When searching for conversations containing a certain user by using
contains
onm
, it's recommended that you stick to the defaultlimit
(which is 10) and make use ofupdatedAt
orlastMessageAt
for pagination. - If your app has too many conversations, consider creating a cloud function that periodically cleans up inactive conversations.
Retrieving Messages
By default, message histories are stored on the cloud for 180 days. You may either pay to extend the period (contact us by submitting a ticket) or synchronize them to your own server with REST API.
Our SDKs offer various ways for you to retrieve message histories. iOS and Android SDKs also provide a caching mechanism to help you reduce the number of queries you have to perform and display message histories to users even when their devices are offline.
Retrieving Messages Chronologically (New to Old)
The most common way to retrieve messages is to fetch them from new to old with the help of pagination:
- Unity
- Android
- iOS
- JavaScript
// limit could be any number from 1 to 100 (defaults to 20)
var messages = await conversation.QueryMessages(limit: 10);
foreach (var message in messages) {
if (message is LCIMTextMessage textMessage) {
}
}
// limit could be any number from 1 to 100; invoking queryMessages without the limit parameter will retrieve 20 messages
int limit = 10;
conv.queryMessages(limit, new LCIMMessagesQueryCallback() {
@Override
public void done(List<LCIMMessage> messages, LCIMException e) {
if (e == null) {
// The last 10 messages retrieved
}
}
});
// Retrieve the last 10 messages; limit could be any number from 1 to 100; use 0 for the default value (20)
[conversation queryMessagesWithLimit:10 callback:^(NSArray *objects, NSError *error) {
NSLog(@"Messages retrieved!");
}];
conversation
.queryMessages({
limit: 10, // limit could be any number from 1 to 100 (defaults to 20)
})
.then(function (messages) {
// The last 10 messages ordered from old to new
})
.catch(console.error.bind(console));
Here queryMessage
supports pagination. Given the fact that you can locate a single message with its messageId
and timestamp, this means that you can retrieve the next few messages after a given message by providing the messageId
and timestamp of that message:
- Unity
- Android
- iOS
- JavaScript
// limit could be any number from 1 to 1000 (defaults to 100)
var messages = await conversation.QueryMessages(limit: 10);
var oldestMessage = messages[0];
var start = new LCIMMessageQueryEndpoint {
MessageId = oldestMessage.Id,
SentTimestamp = oldestMessage.SentTimestamp
};
var messagesInPage = await conversation.QueryMessages(start: start);
// limit could be any number from 1 to 1000 (defaults to 100)
conv.queryMessages(10, new LCIMMessagesQueryCallback() {
@Override
public void done(List<LCIMMessage> messages, LCIMException e) {
if (e == null) {
// The last 10 messages retrieved
// The earliest message will be the first one
LCIMMessage oldestMessage = messages.get(0);
conv.queryMessages(oldestMessage.getMessageId(), oldestMessage.getTimestamp(),20,
new LCIMMessagesQueryCallback(){
@Override
public void done(List<LCIMMessage> messagesInPage,LCIMException e){
if(e== null){
// Query completed
Log.d("Tom & Jerry", "got " + messagesInPage.size()+" messages");
}
}
});
}
}
});
// Retrieve the last 10 messages
[conversation queryMessagesWithLimit:10 callback:^(NSArray *messages, NSError *error) {
NSLog(@"First retrieval completed!");
// Get the messages right before the first message in the first page
LCIMMessage *oldestMessage = [messages firstObject];
[conversation queryMessagesBeforeId:oldestMessage.messageId timestamp:oldestMessage.sendTimestamp limit:10 callback:^(NSArray *messagesInPage, NSError *error) {
NSLog(@"Second retrieval completed!");
}];
}];
// JS SDK encloses the feature into an iterator so you can keep retrieving new data by calling next
// Create an iterator and retrieve 10 messages each time
var messageIterator = conversation.createMessagesIterator({ limit: 10 });
// Call next for the first time and get the first 10 messages; done equals to false means that there are more messages
messageIterator
.next()
.then(function (result) {
// result: {
// value: [message1, ..., message10],
// done: false,
// }
})
.catch(console.error.bind(console));
// Call next for the second time and get the 11th to 20th messages; done equals to false means that there are more messages
// The iterator will keep track of the breaking point so you don't have to specify it
messageIterator
.next()
.then(function (result) {
// result: {
// value: [message11, ..., message20],
// done: false,
// }
})
.catch(console.error.bind(console));
Retrieving Messages by Types
Besides retrieving messages in time orders, you can also do that based on the types of messages. This could be helpful in scenarios like displaying all the images in a conversation.
queryMessage
can take in the type of messages:
- Unity
- Android
- iOS
- JavaScript
// Pass in a generic type parameter and the SDK will automatically read the type and send it to the server for searching messages
var imageMessages = await conversation.QueryMessages(messageType: -2);
int msgType = LCIMMessageType.IMAGE_MESSAGE_TYPE;
conversation.queryMessagesByType(msgType, limit, new LCIMMessagesQueryCallback() {
@Override
public void done(List<LCIMMessage> messages, LCIMException e){
}
});
[conversation queryMediaMessagesFromServerWithType:LCIMMessageMediaTypeImage limit:10 fromMessageId:nil fromTimestamp:0 callback:^(NSArray *messages, NSError *error) {
if (!error) {
NSLog(@"Query completed!");
}
}];
conversation
.queryMessages({ type: ImageMessage.TYPE })
.then((messages) => {
console.log(messages);
})
.catch(console.error);
To retrieve more images, follow the way introduced in the previous section to go through different pages.
Retrieving Messages Chronologically (Old to New)
Besides the two ways mentioned above, you can also retrieve messages from old to new. The code below shows how you can retrieve messages starting from the time the conversation is created:
- Unity
- Android
- iOS
- JavaScript
var earliestMessages = await conversation.QueryMessages(direction: LCIMMessageQueryDirection.OldToNew);
LCIMMessageInterval interval = new LCIMMessageInterval(null, null);
conversation.queryMessages(interval, DirectionFromOldToNew, limit,
new LCIMMessagesQueryCallback(){
public void done(List<LCIMMessage> messages, LCIMException exception) {
// Handle result
}
});
[conversation queryMessagesInInterval:nil direction:LCIMMessageQueryDirectionFromOldToNew limit:20 callback:^(NSArray<LCIMMessage *> * _Nullable messages, NSError * _Nullable error) {
if (messages.count) {
// Handle result
}
}];
var { MessageQueryDirection } = require('leancloud-realtime');
conversation.queryMessages({
direction: MessageQueryDirection.OLD_TO_NEW,
}).then(function(messages) {
// Handle result
}.catch(function(error) {
// Handle error
});
It is a bit more complicated to implement pagination with this method. See the next section for details.
Retrieving Messages Chronologically (From a Timestamp to a Direction)
You can retrieve messages starting from a given message (determined by ID and timestamp) toward a certain direction:
- New to old: Retrieve messages sent before a given message
- Old to new: Retrieve messages sent after a given message
Now we can implement pagination in different directions.
- Unity
- Android
- iOS
- JavaScript
var earliestMessages = await conversation.QueryMessages(direction: LCIMMessageQueryDirection.OldToNew, limit: 1);
// Get messages sent after earliestMessages.Last()
var start = new LCIMMessageQueryEndpoint {
MessageId = earliestMessages.Last().Id
};
var nextPageMessages = await conversation.QueryMessages(start: start);
LCIMMessageIntervalBound start = LCIMMessageInterval.createBound(messageId, timestamp, false);
LCIMMessageInterval interval = new LCIMMessageInterval(start, null);
LCIMMessageQueryDirection direction;
conversation.queryMessages(interval, direction, limit,
new LCIMMessagesQueryCallback(){
public void done(List<LCIMMessage> messages, LCIMException exception) {
// Handle result
}
});
LCIMMessageIntervalBound *start = [[LCIMMessageIntervalBound alloc] initWithMessageId:nil timestamp:timestamp closed:false];
LCIMMessageInterval *interval = [[LCIMMessageInterval alloc] initWithStartIntervalBound:start endIntervalBound:nil];
[conversation queryMessagesInInterval:interval direction:direction limit:20 callback:^(NSArray<LCIMMessage *> * _Nullable messages, NSError * _Nullable error) {
if (messages.count) {
// Handle result
}
}];
var { MessageQueryDirection } = require('leancloud-realtime');
conversation.queryMessages({
startTime: timestamp,
startMessageId: messageId,
startClosed: false,
direction: MessageQueryDirection.OLD_TO_NEW,
}).then(function(messages) {
// Handle result
}.catch(function(error) {
// Handle error
});
Retrieving Messages Within a Period of Time
Besides retrieving messages chronologically, you can also retrieve messages within a period of time. For example, if you already have two messages, you can have one of them to be the starting point and another one to be the ending point to retrieve all the messages between them:
Note: The limit of 100 messages per query still applies here. To fetch more messages, keep changing the starting point or the ending point until all the messages are retrieved.
- Unity
- Android
- iOS
- JavaScript
var earliestMessage = await conversation.QueryMessages(direction: LCIMMessageQueryDirection.OldToNew, limit: 1);
var latestMessage = await conversation.QueryMessages(limit: 1);
var start = new LCIMMessageQueryEndpoint {
MessageId = earliestMessage[0].Id
};
var end = new LCIMMessageQueryEndpoint {
MessageId = latestMessage[0].Id
};
// messagesInInterval contains at most 100 messages
var messagesInInterval = await conversation.QueryMessages(start: start, end: end);
LCIMMessageIntervalBound start = LCIMMessageInterval.createBound(messageId, timestamp, false);
LCIMMessageIntervalBound end = LCIMMessageInterval.createBound(endMessageId, endTimestamp, false);
LCIMMessageInterval interval = new LCIMMessageInterval(start, end);
LCIMMessageQueryDirection direction;
conversation.queryMessages(interval, direction, limit,
new LCIMMessagesQueryCallback(){
public void done(List<LCIMMessage> messages, LCIMException exception) {
// Handle result
}
});
LCIMMessageIntervalBound *start = [[LCIMMessageIntervalBound alloc] initWithMessageId:nil timestamp:startTimestamp closed:false];
LCIMMessageIntervalBound *end = [[LCIMMessageIntervalBound alloc] initWithMessageId:nil timestamp:endTimestamp closed:false];
LCIMMessageInterval *interval = [[LCIMMessageInterval alloc] initWithStartIntervalBound:start endIntervalBound:end];
[conversation queryMessagesInInterval:interval direction:direction limit:100 callback:^(NSArray<LCIMMessage *> * _Nullable messages, NSError * _Nullable error) {
if (messages.count) {
// Handle result
}
}];
conversation.queryMessages({
startTime: timestamp,
startMessageId: messageId,
endTime: endTimestamp,
endMessageId: endMessageId,
}).then(function(messages) {
// Handle result
}.catch(function(error) {
// Handle error
});
Caching Messages
iOS and Android SDKs come with a mechanism that automatically caches all the messages received and retrieved on the local device. It provides the following benefits:
- Message histories can be viewed even devices are offline
- The frequency of querying and the consumption of data can be minimized
- The speed for viewing messages can be increased
Caching is enabled by default. You can turn it off with the following interface:
- Unity
- Android
- iOS
- JavaScript
// Not supported yet
// Need to be set before calling LCIMClient.open(callback)
LCIMOptions.getGlobalOptions().setMessageQueryCacheEnabled(false);
// Need to be set before calling [avimClient openWithCallback:callback]
avimClient.messageQueryCacheEnabled = false;
// Not supported yet
Logging out and Network Changes
Logging out
If your app allows users to log out, you can use the close
method provided by LCIMClient
to properly close the connection to the cloud:
- Unity
- Android
- iOS
- JavaScript
await tom.Close();
tom.close(new LCIMClientCallback(){
@Override
public void done(LCIMClient client,LCIMException e){
if(e==null){
// Logged out
}
}
});
[tom closeWithCallback:^(BOOL succeeded, NSError * _Nullable error) {
if (succeeded) {
NSLog(@"Logged out.");
}
}];
tom
.close()
.then(function () {
console.log("Tom logged out.");
})
.catch(console.error.bind(console));
After the function is called, the connection between the client and the server will be terminated. If you check the status of the corresponding clientId
on the cloud, it would show as "offline".
Network Changes
The availability of the messaging service is highly dependent on the Internet connection. If the connection is lost, all the operations regarding messages and conversations will fail. At this time, there need to be some indicators on the UI to tell the user about the network status.
Our SDKs maintain a heartbeat mechanism with the cloud which detects the change of network status and have your app notified if certain events occur. To be specific, if the connection status changes (becomes lost or recovered), the following events will be populated:
- Unity
- Android
- iOS
- JavaScript
The following events will be populated on LCIMClient
:
OnPaused
occurs when the connection is lost. The messaging service is unavailable at this time.OnResume
occurs when the connection is recovered. The messaging service is available at this time.OnClose
occurs when the connection is closed and there will be no auto reconnection.
The following events will be populated on LCIMClientEventHandler
:
onConnectionPaused()
occurs when the connection is lost. The messaging service is unavailable at this time.onConnectionResume()
occurs when the connection is recovered. The messaging service is available at this time.onClientOffline()
occurs when single-device sign-on is enabled and the current device is forced to go offline.
The following events will be populated on LCIMClientDelegate
:
imClientResumed
occurs when the connection is recovered.imClientPaused
occurs when the connection is lost. Possible causes include a network problem occurred and the application goes into the background.imClientResuming
occurs when trying to reconnect.imClientClosed
occurs when the connection is closed and there will be no auto reconnection. Possible causes include there is a single-device login conflict or the client has been kicked off by the server.
- (void)imClientResumed:(LCIMClient *)imClient
{
}
- (void)imClientResuming:(LCIMClient *)imClient
{
}
- (void)imClientPaused:(LCIMClient *)imClient error:(NSError * _Nullable)error
{
}
- (void)imClientClosed:(LCIMClient *)imClient error:(NSError * _Nullable)error
{
}
DISCONNECT
: Connection to the server is lost. The messaging service is unavailable at this time.OFFLINE
: Network is unavailable.ONLINE
: Network is recovered.SCHEDULE
: Scheduled to reconnect after a period of time. The messaging service is still unavailable at this time.RETRY
: Reconnecting.RECONNECT
: Connection to the server is recovered. The messaging service is available at this time.
var { Event } = require("leancloud-realtime");
realtime.on(Event.DISCONNECT, function () {
console.log("Connection to the server is lost.");
});
realtime.on(Event.OFFLINE, function () {
console.log("Network is unavailable.");
});
realtime.on(Event.ONLINE, function () {
console.log("Network is recovered.");
});
realtime.on(Event.SCHEDULE, function (attempt, delay) {
console.log(
"Reconnecting in " + delay + " ms as attempt " + (attempt + 1) + "."
);
});
realtime.on(Event.RETRY, function (attempt) {
console.log("Reconnecting as attempt " + (attempt + 1) + ".");
});
realtime.on(Event.RECONNECT, function () {
console.log("Connection to the server is recovered.");
});
More Suggestions
Sorting Conversations by Last Activities
In many scenarios you may need to sort conversations based on the time the last message in each of them is sent.
There is a lastMessageAt
property for each LCIMConversation
(lm
in the _Conversation
table) which dynamically changes to reflect the time of the last message. The time is server-based (accurate to a second) so you don't have to worry about the time on the clients. LCIMConversation
also offers a method for you to retrieve the last message of each conversation, which gives you more flexibility to design the UI of your app.
Auto Reconnecting
If the connection between a client and the cloud is not properly closed, our iOS and Android SDKs will automatically reconnect when the network is recovered. You can listen to IMClient
to get updated about the network status.
More Conversation Types
Besides the one-on-one chats and group chats mentioned earlier, the following types of conversations are also supported:
Chat room: This can be used to build conversations that serve scenarios like live streaming. It's different from a basic group chat in the number of members supported and the deliverability promised. See Chapter 3 for more details.
Temporary conversation: This can be used to build conversations between users and customer service representatives. It's different from a basic one-on-one chat in the fact that it has a shorter TTL which brings higher flexibility and lower cost (on data storage). See Chapter 3 for more details.
System conversation: This can be used to build accounts that could broadcast messages to all their subscribers. It's different from a basic group chat in the fact that users can subscribe to it and there isn't a number limit of members. Subscribers can also send one-on-one messages to these accounts and these messages won't be seen by other users. See Chapter 4 for more details.