Skip to main content

二,Advanced Messaging Features, Push Notifications, Synchronization, and Multi Device Sign-on

Introduction

In the previous chapter Basic Conversations and Messages, we introduced how you can create components in your app that support one-on-one chatting and group chats, as well as how you can handle events triggered by the cloud. In this chapter, we will show you how to implement advanced features like:

  • Getting receipts when messages are delivered and read
  • Mentioning people with "@"
  • Recalling and editing messages
  • Push notifications and message synchronization
  • Single device or multi device sign-on
  • Sending messages of your custom types

Advanced Messaging Features

If you are building an app for team collaboration or social networking, you may want more features to be included beside basic messaging. For example:

  • To mention someone with "@" so that they can easily find out what messages are important to them.
  • To edit or recall a piece of message that has been sent out.
  • To send status messages like "Someone is typing".
  • To allow the sender of a message to know if it's delivered or read.
  • To synchronize messages sent to a receiver that has been offline.

With LeanMessage, you can easily implement the functions mentioned above.

Mentioning People

Some group chats have a lot of messages going on and people may easily overlook the information that's important to them. That's why we need a way for senders to get people's attention.

The most commonly used way to mention someone is to type "@ + name" when composing a message. But if we break it down, we'll notice that the "name" here is something determined by the app (it could be the real name or the nickname of the user) and could be totally different from the clientId identifying the user (since one is for people to see and one is for computers to read). A problem could be caused if someone changes their name at the moment another user sends a message with the old name mentioned. Beside this, we also need to consider the way to mention all the members in a conversation. Is it "@all"? "@group"? Or "@everyone"? Maybe all of them will be used, which totally depends on how the UI of the app is designed.

So we cannot mention people by simply adding "@ + name" into a message. To walk through that, LeanMessage allows you to specify two properties with AVIMMessage:

  • mentionList, an array of strings containing the list of clientIds being mentioned;
  • mentionAll, a Bool indicating whether all the members are mentioned.

Based on the logic of your app, it's possible for you to have both mentionAll to be set and mentionList to contain a list of members. Your app shall provide the UI that allows users to type in and select the members they want to mention. The only thing you need to do with the SDK is to call the setters of mentionList and mentionAll to set the members being mentioned. Here is a code example:

Mentioning People

Some group chats have a lot of messages going on and people may easily overlook the information that's important to them. That's why we need a way for senders to get people's attention.

The most commonly used way to mention someone is to type "@ + name" when composing a message. But if we break it down, we'll notice that the "name" here is something determined by the app (it could be the real name or the nickname of the user) and could be totally different from the clientId identifying the user (since one is for people to see and one is for computers to read). A problem could be caused if someone changes their name at the moment another user sends a message with the old name mentioned. Beside this, we also need to consider the way to mention all the members in a conversation. Is it "@all"? "@group"? Or "@everyone"? Maybe all of them will be used, which totally depends on how the UI of the app is designed.

So we cannot mention people by simply adding "@ + name" into a message. To walk through that, LeanMessage allows you to specify two properties with AVIMMessage:

  • mentionList, an array of strings containing the list of clientIds being mentioned;
  • mentionAll, a Bool indicating whether all the members are mentioned.

Based on the logic of your app, it's possible for you to have both mentionAll to be set and mentionList to contain a list of members. Your app shall provide the UI that allows users to type in and select the members they want to mention. The only thing you need to do with the SDK is to call the setters of mentionList and mentionAll to set the members being mentioned. Here is a code example:

var textMessage = new AVIMTextMessage("@Tom Come back early.")
{
MentionList = new List<string>() { "Tom" }
};
await conversation.SendMessageAsync(textMessage);

You can also mention everyone by setting mentionAll:

var textMessage = new AVIMTextMessage("@all")
{
MentionAll = true
};
await conv.SendMessageAsync(textMessage);

The receiver of the message can call the getters of mentionList and mentionAll to see the members being mentioned:

private void OnMessageReceived(object sender, AVIMMessageEventArgs e)
{
if (e.Message is AVIMImageMessage imageMessage)
{
var mentionedList = e.Message.MentionList;
}
}

To make it easier to display things on the UI, the following two flags are offered by AVIMMessage to indicate the status of mentioning:

  • mentionedAll: Whether all the members in the conversation are mentioned. Becomes true only when mentionAll is true, otherwise it remains false.
  • mentioned: Whether the current user is mentioned. Becomes true when mentionList contains the clientId of the current user or when mentionAll is true, otherwise it remains false.

Here is a code example:

private void OnMessageReceived(object sender, AVIMMessageEventArgs e)
{
if (e.Message is AVIMImageMessage imageMessage)
{
var mentionedAll = e.Message.MentionAll;
// Check if the current user is mentioned; requires additional comparison in .NET SDK
var mentioned = e.Message.MentionAll || e.Message.MentionList.Contains("Tom");
}
}

Modify a Message

To allow a user to edit messages they sent, app developers have to enable Allow editing messages with SDK in Dashboard > Messaging > LeanMessage > Settings > LeanMessage settings.

There are no limits on the time within which they can perform this operation. However, users are only allowed to edit messages they sent, not the ones sent by others.

To modify a message, what you would do is not to update the original message instance, but to create a new one and call Conversation#updateMessage(oldMessage, newMessage) to submit the request to the cloud. Here is a code example:

var newMessage = new AVIMTextMessage("The new message.");
await conversation.UpdateAsync(oldMessage, newMessage);

If the modification succeeded, other members in the conversation will receive a MESSAGE_UPDATE event:

tom.OnMessageUpdated += (sender, e) => {
var message = (AVIMTextMessage) e.Message; // e.Messages contains the messages being updated; it is a collection of messages since the SDK may combine multiple operations into a single request
Debug.Log(string.Format("Message with ID {1} is updated to {0}", message.TextContent, message.Id));
};

For Android and iOS SDKs, if caching is enabled (enabled by default), the SDKs will first update the modified message in the cache and then trigger an event to the app. When you receive such event, simply refresh the chatting page to reflect the latest collection of messages.

If a message is modified by the system (for example, due to keyword filtering or by a hook on LeanEngine), the sender will receive a MESSAGE_UPDATE event, and other members in the conversation will receive the modified message.

Recall a Message

Besides modifying a sent message, a user can also recall a message they sent. Similarly, app developers need to enable this in application dashboard (Dashboard > Messaging > LeanMessage > Settings > LeanMessage settings Allow recalling messages with SDK). Also, there are no limits on the time within which they can perform this operation, and users are only allowed to recall messages they sent, not the ones sent by others.

To recall a message, invoke the Conversation#recallMessage method:

await conversation.RecallAsync(message);

Once recalling a message succeeded, other members in the conversation will receive the MESSAGE_RECALL event:

tom.OnMessageRecalled += Tom_OnMessageRecalled;
private void Tom_OnMessageRecalled(object sender, AVIMMessagePatchEventArgs e)
{
// e.Messages contains the messages being edited; it is a collection of messages since the SDK may combine multiple operations into a single request
}

For Android and iOS SDKs, if caching is enabled (enabled by default), the SDKs will first delete the recalled message from the cache and then trigger an event to the app. This ensures the consistency of data internally. When you receive such event, simply refresh the chatting page to reflect the latest collection of messages. Based on your implementation, either the total amount of messages would become less or a message indicating message being recalled would be displayed.

Transient Messages

Sometimes we need to send status updates like "Someone is typing…" or "Someone changed the group name to XX". Different from messages sent by users, these messages don't need to be stored to the history, nor do they need to be guaranteed to be delivered (if members are offline or there is a network error, it would be okay if these messages are not delivered). Such messages are best sent as transient messages.

Transient message is a special type of message. It has the following differences comparing to a basic message:

  • It won't be stored to the cloud so it couldn't be retrieved from history messages.
  • It's only delivered to those who are online. Offline members cannot receive it later or get push notifications about it.
  • It's not guaranteed to be delivered. If there's a network error preventing the message from being delivered, the server won't make a second attempt.

Therefore, transient messages are best for communicating real-time updates of statuses that are changing frequently or implementing simple control protocols.

The way to construct a transient message is the same as that for a basic message. The only difference is the way it is being sent. So far we have shown the following way for sending messages with AVIMConversation:

/**
* Send a message
* @param {Message} message The message itself; an instance of Message or its subtype
* @return {Promise.<Message>} The message being sent
*/
async send(message)

In fact, an additional parameter AVIMMessageOption can be provided when sending a message. Here is a complete list of interfaces offered by AVIMConversation:

/// <summary>
/// Send a message
/// </summary>
/// <param name="avMessage">The message itself</param>
/// <param name="options">Options<see cref="AVIMSendOptions"/></param>
/// <returns></returns>
public Task<IAVIMMessage> SendMessageAsync(IAVIMMessage avMessage, AVIMSendOptions options);

With AVIMMessageOption, we can specify:

  • Whether it is a transient message (field transient).
  • Whether receipts are needed (field receipt; more details will be covered later).
  • The priority of the message (field priority; more details will be covered later).
  • Whether it is a will message (field will; more details will be covered later).
  • The content for push notification (field pushData; more details will be covered later); if the receiver is offline, a push notification with this content will be triggered.

The code below sends a transient message saying "Tom is typing…" to the conversation when Tom's input box gets focused:

var textMessage = new AVIMTextMessage("Tom is typing…")
{
MentionAll = true
};
var option = new AVIMSendOptions(){Transient = true};
await conv.SendAsync(textMessage, option);

The procedure for receiving transient messages is also the same as that for basic messages. You can run different logic based on the types of messages. The example above sets the type of the message to be text message, but it would be better if you assign a distinct type to it. Our SDK doesn't offer a type for transient messages, so you may build your own depending on what you need. See Custom Message Types for more details.

Receipts

When the cloud is delivering messages, it follows the sequence the messages are pushed to the cloud and delivers the former messages in advance of the latter ones (FIFO). Our internal protocol also requires that the SDK sends an acknowledgement (ack) back to the cloud for every single message received by it. If a message is received by the SDK but the ack is not received by the cloud due to a packet loss, the cloud would assume that the message is not successfully delivered and will keep redelivering it until an ack is received. Correspondingly, the SDK also does it work to make duplicate messages insensible by the app. The entire mechanism ensures that no message will be lost in the entire delivery process.

However, in certain scenarios, functionality beyond the one mentioned above is demanded. For example, a sender may want to know when the receiver got the message and when they opened the message. In a product for team collaboration or private communication, a sender may even want to monitor the real-time status of every message sent out by them. Such requirements can be satisfied with the help of receipts.

Similar to the way of sending transient messages, if you want receipts to be given back, you need to specify an option in AVIMMessageOption:

var textMessage = new AVIMTextMessage("A very important message.");
var option = new AVIMSendOptions(){Receipt = true};
await conv.SendAsync(textMessage, option);

Note:

Receipts are not enabled by default. You need to manually turn that on when sending each message. Receipts are only available for conversations with no more than 2 members.

So how do senders handle the receipts they get?

Delivery Receipts

When a message is delivered to the receiver, the cloud will give a delivery receipt to the sender. Keep in mind that this is not the same as a read receipt.

// Tom creates an AVIMClient with his name as clientId
AVIMClient client = new AVIMClient("Tom");

// Tom logs in
await client.ConnectAsync();

// Enable delivery receipt
conversaion.OnMessageDeliverd += (s, e) =>
{
// Things to do after messages are delivered
};
// Send message
await conversaion.SendTextMessageAsync("Wanna go to bakery tonight?");

The content included in the receipt will not be a specific message. Instead, it will be the time the messages in the current conversation are last delivered (lastDeliveredAt). We have mentioned earlier that messages are delivered according to the sequence they are pushed to the cloud. Therefore, given the time of the last delivery, we can infer that all the messages sent before it are delivered. On the UI of the app, you can mark all the messages sent before lastDeliveredAt to be "delivered".

Read Receipts

When we say a message is delivered, what we mean is that the message is received by the client from the cloud. At this time, the actual user might not have the conversation page or even the app open (Android apps can receive messages in the background). So we cannot assume that a message is read just because it is delivered.

Therefore, we offer another kind of receipt showing if a receiver has actually seen a message.

When a user opens a conversation, we can say that the user has read all the messages in it. You can use the following interface of Conversation to mark all the messages in it as read:

// Not supported yet

After the receiver has read the latest messages, the sender will get a receipt indicating that the messages they have sent out are read.

So if Tom is chatting with Jerry and wants to know if Jerry has read the messages, the following procedure would apply:

  1. Tom sends a message to Jerry and requests receipts on it:

    // Not supported yet
  1. Jerry reads Tom's message and call read on the conversation to mark the latest messages as read:

    // Not supported yet
  2. Tom gets a read receipt with the conversation's lastReadAt updated. The UI can be updated to mark all messages sent before lastReadAt to be read:

    // Not supported yet

Note:

To use read receipts, turn on notifications on updates of unread message count when initializing your app.

Muting Conversations

If a user doesn't want to receive notifications from a conversation but still wants to stay in it, they can mute the conversation. See Muting Conversations in the next chapter for more details.

Will Messages

Will message can be used to automatically notify other members in a conversation when a user goes offline unexpectedly. It gets its name from the wills filed by testators, giving people a feeling that the last messages of a person should always be heard. It looks like the message saying "Tom is offline and cannot receive messages" in this image:

在一个名为「Tom &amp; Jerry」的对话中,Jerry 收到内容为「Tom 已断线,无法收到消息」的 Will 消息。这条消息看起来像一条系统通知,与普通消息的样式不同。

A will message needs to be composed ahead of time and cached on the cloud. The cloud doesn't send it out immediately after receiving it. Instead, it waits until the sender of it goes offline unexpectedly. You can implement your own logic to handle such event.

var message = new AVIMTextMessage()
{
TextContent = "I am a will message. I will be sent out to other members in the conversation when the sender goes offline unexpectedly."
};
var sendOptions = new AVIMSendOptions()
{
Will = true
};
await conversation.SendAsync(message, sendOptions);

Once the sender goes offline unexpectedly, other members will immediately receive the will message. You can design your own way to display it on the UI.

Will message has the following restrictions:

  • Each user can only have one will message set up at a time. This means that if a user sets will messages for multiple conversations or multiple will messages for the same conversation, only the last one will take effect.
  • Will messages don't get stored to the history.
  • If a user logs out proactively, the will message set by this user will not be sent out (if there is one).

Keyword Filtering

If you app allows users to create group chats, you might consider filtering cuss words out from the messages sent by users. LeanMessage offers a built-in component that helps you easily implement such function. See Keyword Filtering in the next chapter for more details.

Handling Undelivered Messages

Sometimes you may need to store the messages that are not successfully sent out into a local cache and handle them later. For example, if a client's connection to the server is lost and a message cannot be sent out due to this, you may still keep the message locally. Perhaps you can add an error icon and a button for retrying next to the message displayed on the UI. The user may tap on the button when the connection is recovered to make another attempt to send the message.

By default, both Android and iOS SDKs enable a local cache for storing messages. The cache stores all the messages that are already sent to the cloud and keeps itself updated with the data in the cloud. To make things easier, undelivered messages can also be stored in the same cache.

The code below adds a message to the cache:

// Not supported yet

The code below removes a message from the cache:

// Not supported yet

When reading messages from the cache, you can make messages look different on the UI based on the property message.status. If the status of a message is AVIMMessageStatusFailed, it means the message cannot be sent out, so you can add a button for retrying on the UI. An additional benefit for using the local cache is that the SDK will make sure the same message only gets sent out once. This ensures that there won't be any duplicate messages on the cloud.

Push Notifications

If your users are using your app on mobile devices, they might close the app at anytime, which prevents you from delivering new messages to them in the ordinary way. At this time, using push notifications becomes a good alternative to get users notified when new messages are coming in.

If you are building an iOS or Android app, you can utilize the built-in push notification services offered by these operating systems, as long as you have your certificates configured for iOS or have the function enabled for Android. Check the following documentation for more details:

  1. Push Notification Overview
  2. Android Push Notification Guide / iOS Push Notification Guide

LeanCloud offers a complete set of push notification services that works perfectly with LeanMessage. When being implemented, it links the clientIds of users with the _Installation table of your app that keeps track of the device information. When a user sends a message to a conversation, the cloud will automatically convert the message to a push notification and send it to those who are offline but are using iOS devices or using Android devices with push notification services enabled. We also allow you to connect third-party push notification services to your app.

The highlight of this feature is that you can customize the contents of push notifications. You have the following three ways to specify the contents:

  1. Setting up a static message

    You can fill in a global static JSON string in your app's Dashboard > Messaging > LeanMessage > Settings > Push notification settings for delivering push notifications with a static message. For example, if you put:

    { "alert": "New message received", "badge": "Increment" }

    Then whenever there is a new message going to an offline user, the user will receive a push notification saying "New message received".

    Keep in mind that badge is for iOS devices only which means to increase the number displayed on the badge of the app. Its value Increment is case-sensitive. Typically, when an end user opens or closes the application, you need to set the value of the badge field of the _Installation class to zero, which clears the badge number. Besides, you can also customize the sounds of push notifications for iOS devices.

  2. Specifying contents when sending messages from a client

    When using the first way introduced above, the content included in each push notification is the same regardless of the message being sent out. Is it possible to dynamically generate these contents to make them relevant to the actual messages?

    Remember how we specified the AVIMMessageOption parameter when sending transient messages? The same parameter takes in a pushData property which allows you to specify the contents of push notifications. Here is a code example:

  var message = new AVIMTextMessage()
{
TextContent = "Hey Jerry, me and Kate are going to a bar to watch a game tonight. Do you wanna come?"
};


AVIMSendOptions sendOptions = new AVIMSendOptions()
{
PushData = new Dictionary<string, object>()
{
{ "alert", "New message received"},
{ "category", "Message"},
{ "badge", 1},
{ "sound", "message.mp3"}, // The name of the sound file; has to be present in the app
{ "custom-key", "This is a custom attribute. custom-key is just an example. You can use your own names for keys."}
}
};
  1. Generating contents dynamically on the server side

    The second way introduced above allows you to compose the contents of push notifications based on the messages being sent out, but the logic needs to be predefined on the client side, which makes things less flexible.

    So we offer a third way that allows you to define the logic of push notifications on the server side with the help of hooks. Check the following documentation for more details:

Here is a comparison of the priorities of the three methods mentioned above: Generating contents dynamically on the server side > Specifying contents when sending messages from a client > Setting up a static message.

If more than one of these methods are implemented at the same time, the push notifications generated on the server side will always get the highest priority. Those sent from clients will get the secondary priority, and the static message set up on the dashboard will get the lowest priority.

Implementations and Restrictions

If your app is using push notification services together with LeanMessage, when a client is logging in, the SDK will automatically associate the clientId with the device information (stored in the Installation table) by having the device subscribe to the channel with clientId as its name. Such association can be seen in the channels field of the _Installation table. By doing so, when the cloud wants to send a push notification to a client, the client's device can be targeted with the clientId associated with it.

Since LeanMessage generates way more push notifications than other sources, the cloud will not keep any records for it, nor can you find them in your app's Dashboard > Messaging > Push notifications > History.

Each push notification is only valid for 7 days. This means that if a device doesn't connect to the service for more than 7 days, it will not receive the push notification anymore.

Advanced Settings for Push Notifications

When push notifications are sent via iOS Token Authentication, if your app has private keys for multiple Team IDs, please confirm the one that should be used for your target devices and fill it into the _apns_team_id parameter, since Apple doesn't allow a single request to include push notifications sent to devices belonging to different Team IDs.

{
"alert": "New message received",
"_apns_team_id": "my_fancy_team_id"
}

When push notifications are sent via certificates, production certificate will be used by default. You can specify the certificate you want to use by adding a _profile property into the JSON string:

{
"alert": "New message received",
"_profile": "dev"
}

_profile is only applicable to push notifications sent via certificates, and is not applicable to push notifications sent via iOS Token Authentication.

_profile and _apns_team_id will not be included in the actual contents of push notifications. Apple recommends using iOS Token Authentication.

For the message set up in your app's Dashboard > Messaging > LeanMessage > Settings > Push notification settings, you can include the following variables:

  • ${convId} The ID of the conversation
  • ${timestamp} The Unix timestamp when the push notification is triggered
  • ${fromClientId} The clientId of the sender

Message Synchronization

Push notification seems to be a good way to remind users of new messages, but the actual messages won't get delivered until the user goes online. If a user hasn't been online for an extremely long time, there will be tons of messages piled up on the cloud. How can we make sure that all these messages will be perfectly delivered once the user goes online?

LeanMessage offers two methods for synchronizing messages:

  • One is to have the cloud push messages to the client. The cloud keeps track of the last message each user receives from each conversation. When a user goes online, the cloud will automatically push new messages generated in each conversation to the client, with one push notification for each message (the client can handle them in the same way as receiving new messages). For each conversation, the cloud will deliver at most 20 messages and the rest of them will not be delivered.
  • Another one is to have the client pull messages from the cloud. The cloud keeps track of the last message each user receives from each conversation. When a user goes online, the conversations containing new messages as well as the number of unread messages in each of them will be computed and the client will receive a notification indicating that there is an update on the total number of unread messages. The client can then proactively fetch these messages.

The first method is relatively easier to implement, but since the cloud only pushes no more than 20 messages for each conversation, this method won't work for many of the scenarios, including those that demand displaying the progress of each user reading messages or the exact number of unread messages. Therefore, we highly recommend that you use the second method to have the client pull messages from the cloud.

For historical reasons, different SDKs have different supports for the two methods introduced above:

  1. Android and iOS SDKs support both of them and use the first method (pushing) by default.
  2. JavaScript SDK supports the second method (pulling) by default.
  3. .NET SDK doesn't support the second method yet.

Please don't use different methods for different platforms at the same time (for example, to use the first method for iOS and the second one for Android). This will make your app unable to fetch messages correctly.

Notifications on Updates of Unread Message Count

To know when to pull messages from the cloud, the client relies on the notifications sent from the cloud regarding updates of the numbers of unread messages in all the conversations the current user is in. These numbers are computed at the moment the client goes online.

To receive such notifications, the client needs to indicate that it is using the method of pulling messages from the cloud. As mentioned earlier, JavaScript SDK uses this method by default, so there isn't any configuration that needs to be done. For Android and iOS SDKs, the following line needs to be added when initializing AVOSCloud to indicate that this method will be used:

// Not supported yet

The SDK will maintain an unreadMessagesCount field on each AVIMConversation to track the number of unread messages in the conversation.

When the client goes online, the cloud will drop in a series of <Conversation, UnreadMessageCount, LastMessage> in the format of events indicating updates on the numbers of unread messages. Each of them matches a conversation containing new messages and serves as the initial value of a <Conversation, UnreadMessageCount> maintained on the client side. After this, whenever a message is received by the SDK, the corresponding unreadMessageCount will be automatically increased. When the number of unread messages of a conversation is cleared, both <Conversation, UnreadMessageCount> on the cloud and maintained by the SDK will be reset.

If the count of unread messages is enabled, it will keep increasing until the developer explicitly resets it. It will not be reset automatically if the client goes offline again. Even if a message is received when the client is online, the count will still increase. Make sure to reset the count by marking conversations as read whenever needed.

When the number of <Conversation, UnreadMessageCount> changes, the SDK will send an UNREAD_MESSAGES_COUNT_UPDATE event to the app through IMClient. You can listen to this event and make corresponding changes to the number of unread messages on the UI. We recommend developers to cache unread counts at the application level.

// Not supported yet

When responding to an UNREAD_MESSAGES_COUNT_UPDATE event, you get a Conversation object containing the lastMessage property which is the last message received by the current user from the conversation. To display the actual unread messages, fetch the messages that come after it.

The only way to clear the number of unread messages is to mark the messages as read with Conversation#read. You may do so when:

  • The user opens a conversation
  • The user is already in a conversation and a new message comes in

Implementation details on unread message counts for iOS and Android SDKs:

iOS SDKs (Objective-C and Swift) will fetch all UNREAD_MESSAGES_COUNT_UPDATE events provided by the cloud on login, while Android SDK only fetches the latest events generated after the previous fetch (Android SDK remembers the timestamp of the last fetch).

Therefore, Android developers need to cache the events for unread messages at the application level, because the events of some conversations have been fetched on previous logins, but not the current one. For iOS developers, they need to do the same thing because the cloud tracks at most 50 conversations containing unread messages, and the events for unread messages are only available for those conversations. If the events for unread messages are not cached, some conversations may have inaccurate counts of unread messages.

Multi Device Sign-on and Single Device Sign-on

In some scenarios, a user can stay logged in on multiple devices at the same time. In other ones, a user can be logged in on only one device at a time. With LeanMessage, you can easily implement both multi device sign-on and single device sign-on depending on your needs.

When creating an IMClient instance, you can provide a tag parameter beside clientId to have the cloud check the uniqueness of <ClientId, Tag> when logging the user in. If the user is already logged in on another device with the same tag, the cloud will log the user out from that device, otherwise the user will stay logged in on all their devices. When a user is logged in on multiple devices, the messages coming to this user will be delivered to all these devices and the numbers of unread messages will be synchronized. If the user sends a message from one of these devices, the message will appear on other devices as well.

You can assign a unique tag for each type of device. For example, if you have Mobile for phones, Pad for tablets, and Web for desktop computers, a user will be able to stay logged in on three devices with different types, but not two desktop computers.

If tag is not specified when logging in, a user will be able to have any number of devices logged in at the same time. If all the clients share the same tag, a user will be able to stay logged in on only one device at a time.

Setting Tags

The code below sets a tag called Mobile when creating IMClient, which can be used for the mobile client of your app:

AVIMClient tom = await realtime.CreateClientAsync("Tom", tag: "Mobile", deviceId: "your-device-id");

With the code above, if a user logs in on one mobile device and then logs in on another one (with the same tag), the user will be logged out from the former one.

Handling Conflicts

When the cloud encounters the same <ClientId, Tag> for a second time, the device used for the earlier one will be logged out and receive a CONFLICT event:

tom.OnSessionClosed += Tom_OnSessionClosed;
private void Tom_OnSessionClosed(object sender, AVIMSessionClosedEventArgs e)
{
}

The reason a device gets logged out will be included in the event so that you can display a message to the user with that.

All "logins" mentioned above refer to users' explicit logins. If the user has already logged in, when the application restarts or reconnects, the SDK will relogin automatically. Under these scenarios, if a login conflict is encountered, the cloud will not logout earlier devices. The device trying to relogin will receive an error instead.

Similarly, if the application developer wants an explicit login to receive an error when encountering a conflict, they can pass a special parameter on login:

// Not supported yet

Custom Message Types

Although LeanMessage already support a number of common message types by default, you can still define your own types as you need. For example, if you want your users to send messages containing payments and contacts, you can implement that with custom message types.

Custom Message Attributes

The following message types are offered by default:

  • TextMessage Text message
  • ImageMessage Image message
  • AudioMessage Audio message
  • VideoMessage Video message
  • FileMessage File message (.txt, .doc, .md, etc.)
  • LocationMessage Location message

When composing messages with these types, you can include additional information by attaching custom attributes in the format of key-value pairs. For example, if your are sending a message and need to include city information, you can put it into attributes of the message rather than create your own message type.

var messageWithCity = new AVIMTextMessage("It's too cold now.");
messageWithCity["city"] = "Montreal";

Creating Your Own Message Types

When built-in types cannot fulfill your requirements at all, you can implement custom message type.

By inheriting from `AVIMTypedMessage`, you can define your own types of messages. The basic steps include: * Define a subclass inherited from `AVIMTypedMessage`. * Register the subclass when initializing.
// Provide a class name
[AVIMMessageClassName("InputtingMessage")]
// Assign a value to the type; can only be positive integers; negative numbers are for types offered by default
[AVIMTypedMessageTypeIntAttribute(2)]
public class InputtingMessage : AVIMTypedMessage
{
public InputtingMessage() { }
// We can include an Emoji when sending messages; here we use Ecode for the code of the Emoji
[AVIMMessageFieldName("Ecode")]
public string Ecode { get; set; }
}

// Register subclass
realtime.RegisterMessageType<InputtingMessage>();

See Back to Receiving Messages in the previous chapter for more details on how to receive messages with custom types.

Continue Reading

3. Security, Permission Management, Chat Rooms, and Temporary Conversations

4. Hooks and System Conversations