BlackBerry Spark Communications Services Guide

Messages

Overview

Within chats, participants contribute content by posting messages. Typically, these messages represent user-visible content, but they can also contain arbitrary machine-readable information that your application can consume. Chat messages can be both published by and subscribed to by many users and endpoints for real-time delivery and updates.

Message Lifecycle

Your application constructs messages and sends them using the SDK. A message's content holds text that the user wants other participants to read. Typically, this content is based on user input, such as the text that the user typed into an input field, however, your application can generate a message's content based on your business logic.

In the code snippet below we show how your application can send a basic text message in a chat:

// Obtain an SDK instance.
const bbmsdk = ...;

// Identify the chat
const chatId = "...";

// Send a basic text message
bbmsdk.messenger.chatMessageSend(chatId, {
  tag: 'Text',
  content: 'Hello World !!!'
});
// Identify the existing chat
final String chatId = "...";

// Send a basic text message
ChatMessageSend chatMessageSend = new ChatMessageSend(chatId, ChatMessageSend.Tag.Text)
        .content("Hello World !!!");
BBMEnterprise.getInstance().getBbmdsProtocol().send(chatMessageSend);
// Identify the existing chat
NSString *chatId = @"..."

// Send a basic text message
BBMChatMessageSendMessage *send = [[BBMChatMessageSendMessage alloc] initWithChatId:chatId 
                                                                                tag:kBBMChatMessageSendMessage_TagText];
send.content = @"Hello World !!!";
[[BBMEnterpriseService sharedService] sendMessageToService:send];
// Identify the existing chat
let chatId = "..."

// Send a basic text message
let chatMessage = BBMChatMessageSendMessage(chatId: chatId, tag: kBBMChatMessageSendMessage_TagText)!
chatMessage.content = "Hello World!!!"
BBMEnterpriseService.shared().sendMessage(toService: chatMessage);
    

See the APIs for sending a message in a chat for Android, iOS, Linux, and JavaScript.

A chat message transitions through several states as it propagates its way through the BlackBerry Infrastructure from the sender's local endpoint to all participants in the chat, including the sender's other endpoints. A chat message is first queued, then sent, then delivered, and read. Messages are marked as incoming when the sender is not the local user.

The following table describes the states that a message transitions through:

Message State Description
Sending The message is in the process of being sent to the BlackBerry Infrastructure.
Sent The message has reached the BlackBerry Infrastructure.
Failed The message was not sent.
Delivered The message has been delivered to at least one participant of the chat.
Read The message has been read by at least one participant of the chat.

When an incoming chat message reaches an endpoint, its SDK automatically reports a delivered status back to the BlackBerry Infrastructure. Your application can provide assurances of delivery to the end user by visually representing the Delivered state of messages.

Your application can mark incoming messages as Read by specifying the ID of the most recent message read by the local user. The SDK will automatically transition all older messages in the chat to the Read state. By visually indicating when a message is read by other participants, your application promotes engagement among participants of the chat, and provides your users additional assurances when their messages are read. Your application can define the user interaction triggers that mark incoming chat messages as Read. Some common triggers include:

See the example applications for a reference implementation of common messaging UIs.

In the code snippet below we show how your application can mark messages in a chat as Read:

// Obtain an SDK instance.
const bbmsdk = ...;

// Identify the chat and message
const chatId = "...";
const messageId = ...;

// Mark all incoming messages with an ID less than or equal to 'messageId' as read.
bbmsdk.messenger.chatMessageRead(chatId, messageId);
// Identify the existing chat and message
final String chatId = "...";
final long messageId = ...;

// Mark all incoming messages with an ID less than or equal to 'messageId' as read.
BBMEnterprise.getInstance().getBbmdsProtocol().send(new ChatMessageRead(chatId, messageId));
// Identify the existing chat and message
NSString *chatId = @"...";
unsigned long long messageId = ...;

// Mark all incoming messages with an ID less than or equal to 'messageId' as read.
BBMChatMessageReadMessage *markAsRead = [[BBMChatMessageReadMessage alloc] initWithChatId:chatId 
                                                                                messageId:messageId];
[[BBMEnterpriseService service] sendMessageToService:markAsRead];
// Identify the existing chat and message
let chatId = "..."
let messageId : UInt64 = ...

// Mark all incoming messages with an ID less than or equal to 'messageId' as read.
let markAsRead = BBMChatMessageReadMessage(chatId: chatId, messageId: messageId)
BBMEnterpriseService.shared().sendMessage(toService: markAsRead)
    

See the APIs for marking messages as Read for Android, iOS, Linux, and JavaScript.

The Delivered and Read states are tracked separately for each participant in each chat across all endpoints. A message's sender can query the SDK for the list of per-recipient message delivery state in a chat. When all participants in a chat have the same Delivered or Read state, the messages's isPartial field is set to false. This saves your application the trouble of computing a message's overall Delivered or Read state.

Messages: Partial State

See the APIs for getting a message's delivery state details for Android, iOS, Linux, and JavaScript.

A primary security principle that Spark Communications Services complies with is that messages are digitally signed, so you're assured of who sends each message in your application. The SDK verifies a message's signature to confirm that it is from the claimed sender. When verification fails, a chat message's unverified flag is set, but the content is made available to be processed or ignored as your application sees fit. There are legitimate cases where a user can lose the identity keys that have been used to sign their messages. For example, a user forgets the password that protects their identity keys, and your application opts to allow them to regain access to their Spark Communications identity and generate new keys.

Messages: Unverified

See the chat message APIs for Android, iOS, Linux, and JavaScript.

Processing Messages

The SDK allows your application to query for a message by providing its ID and the ID of the chat it belongs to. A chat message has several attributes typically used in building user interfaces or in processing messages in automated endpoints.

A Spark chat message has:

Messages: Display Message State

When a message is posted to the chat, it typically appears below all of the earlier messages already in the chat. This series of messages is referred to as the message history. Each chat has its own message history which is accessible only to participants of the chat. The SDK and BlackBerry Infrastructure work together to reconcile their views of a chat's message history and recover any messages that failed initial delivery. When messages can't be posted to chats, senders know with clear and definitive error reporting.

On Android, iOS, and Linux, message IDs are consecutive integers, ordered by arrival time. A chat exposes the ID of the most recent message added to the chat (lastMessage), and the total number of messages in the chat (numMessages). These values are kept up to date by the SDK, and can be used to calculate the range of valid integers that is the IDs of messages in the chat history. For example, consider a chat that has the following properties.

Chat Property Value
lastMessage 29
numMessages 20

In such a chat, the inclusive range of integers [10-29] is the list of IDs of all message in the chat. Your application can chose to fetch messages in chunks from this range as it sees fit.

See the APIs for fetching a chat message on Android, iOS, and Linux.

With the SDK for JavaScript, chat messages are fetched on demand from the BlackBerry Infrastructure. Your application can specify a minimum number of messages to retrieve or the ID of a specific message to sync back to. Once fetched, messages will remain cached by the SDK. Your application can query for locally cached messages, and they are returned as an array, ordered by message ID.

See the APIs for fetching chat messages in JavaScript.

Displaying Messages

You might be concerned about implementing an efficient and dynamic message list in a user interface. It can be a daunting task with a lot to think about:

With Spark Communications Services, BlackBerry is sharing the experience it has gained from building messaging applications for more than a decade. The Support libraries and example applications contain reusable components that you can integrate as-is or customize to match your application's styling.

Tags

Your application can give each message a tag to specify how to interpret its content and metadata on the sending and receiving endpoints. The tag may include any arbitrary value that is meaningful to your application.

A headless, automated endpoint typically uses tags to distinguish between its own application protocol messages that it is embedding in the message metadata fields.

For example, your application can define Announcement as a custom type of text messages. In addition to visually representing Announcements differently than other messages, your application can restrict the sending of Announcement messages to chat administrators. Your application can also give priority notifications to messages with the Announcement tag.

Messages: Announcements

You can apply the same principle to messages that carry a voice note as an attachment. Your app can chose a unique layout for messages with a VoiceNote tag, allowing the user to play the recording inline in the chat.

Messages: Voice Note

Your application can query the SDK for a list of chat messages with a specific tag value. For example, your application can get all Announcement messages in a chat. You can utilize this capability in your application to show lists of messages of specific of types.

Messages: Filter by criteria

In the code snippet below we show how your application can get chat messages that match a specific criteria:

// Obtain an SDK instance.
const bbmsdk = ...;

// Identify the chat
const chatId = "...";

// The JavaScript SDK fetches messages on demand, so fetch messages for the
// chat first.
await bbmsdk.messenger.fetchChatMessages(chatId);

// Create a criteria to filter for ChatMessages with the 'Announcements' tag
const criteria = message => message.tag === 'Announcements';

// Get the list of chat messages matching our criteria
const announcementMessages = bbmsdk.messenger.getChatMessages(chatId).filter(criteria);
// Identify the chat
final String chatId = "...";

// Create a criteria to filter for ChatMessages with the 'Announcements' tag
ChatMessageCriteria criteria = new ChatMessageCriteria();
criteria.chatId(chatId);
criteria.tag("Announcements");

// Get the list of chat messages matching our criteria
ObservableList<ChatMessage> announcementMessages =
      BBMEnterprise.getInstance().getBbmdsProtocol().getChatMessageList(criteria);
// Identify the chat
NSString *chatId = @"..."

// Create a criteria object
BBMChatMessageCriteria *criteria = [[BBMChatMessageCriteria alloc] init];
criteria.chatId = chatId;
criteria.tag = @"Announcements";

// Returns a list of all messages where message.tag == "Announcements"
BBMLiveList *list = [[[BBMEnterpriseService sharedService] model] chatMessageWithCriteria:criteria];
// Identify the chat
let chatId = "..."

let criteria = BBMChatMessageCriteria()
criteria.chatId = chatId
criteria.tag = "Announcements"

// Returns a list of all messages where message.tag == "Announcements"
let list = BBMEnterpriseService.shared().model().chatMessage(with: criteria)
    

See the APIs to fetch messages with a specific tag for Android, iOS, Linux, and JavaScript.

See the Announcements application for Android to explore more the concept of custom tags.

Reserved Tags

The SDK will send messages in chats to communicate that certain events have occured. For example, a message with a Subject tag is sent to indicate that the sender has changed the chat's subject. The SDK reserves the following tags for its own use: Join, Leave, Subject, Gap, Shred, Clear, Admin, and Remove. Messages from your application will be ignored if they use a reserved tags.

Message Metadata

Messages have fields that allow your application to attach data to a chat message. Your application can attach:

Your application can use these opaque JSON data fields for application defined message metadata. For example, your application can use the localData field to mark a message as hidden to the local endpoint. In a more sophisticated example, your application can allow the user to add a private sticky note to a message.

An headless, automated endpoint typically uses the data field to carry its own application protocol JSON messages directly. This clean separation between the machine-readable data field and human-readable content fields helps you organize your application and its communication, and operate in mixed environments where some endpoints are automated and some have user interfaces. By keeping machine-readable data separate, you never risk it being misinterpreted, corrupted, or displayed as human-readable text. This makes integration easier and it is part of a secure approach to communication.

In the code snippet below we show how your application can mark a chat message as hidden:

// Obtain an SDK instance.
const bbmsdk = ...;

// Identify the message
const messageId = "...";

// Since the JavaScript SDK does not store data persistently, localData is
// not supported, however similar behaviour can be implemented by the user with
// a Map.
const messageExtraDataMap = new Map();

// Add the "hidden" field to the data
messageExtraDataMap.set(messageId, {hidden: true});
// Identify the chat
ChatMessage message = ...;

// Add the "hidden" field to the data
JSONObject localData = message.localData != null ? message.localData : new JSONObject();
localData.put("hidden", true);

// Send the local data change
ChatMessage.AttributesBuilder builder = new ChatMessage.AttributesBuilder().localData(localData);
BBMEnterprise.getInstance().getBbmdsProtocol().send(message.requestListChange(builder));
// Identify the chatMessage
BBMChatMessage *message = ...

// Copy the existing data or create a new dictionary
NSMutableDictionary *localData = [message.localData mutableCopy] ?: [NSMutableDictionary dictionary];

// Add the "hidden" field
localData[@"hidden"] = @(YES);

// Send the listChange request
NSArray *elements = @[["chatId" : message.chatId, @"messageId" : @(message.messageId), @"localData" : localData]];
BBMRequestListChangeMessage *listChange = [[BBMRequestListChangeMessage alloc] initWithElements:elements 
                                                                                           type:@"chatMessage"];
[[BBMEnterpriseService sharedService] sendMessageToService:listChange];
// Identify the chatMessage
let message : BBMChatMessage = ...

// Copy any existing chat data or create a new JSON dictionary
var localData = message.localData ?? Dictionary()

// Add the "hidden" field
localData["hidden"] = true

// Send the listChange request
let dataElements = [["chatId" : message.chatId, "messageId" : message.messageId,  "localData" : localData]]
let listChange = BBMRequestListChangeMessage(elements: dataElements, type: "chatMessage")
BBMEnterpriseService.shared().sendMessage(toService: listChange)
    

Your application can utilize the data field to associate metadata with the message that is visible to all chat participants. For example, your application can set a priority on chat messages.

Messages: Priority

Your application can associate different metadata depending on the message tag. Whereas a priority can be applicable to all message types, a HealthRecord message can carry additional metadata in the data field that is unique to that message type. When the message tag is HealthRecord, your application can, for example, expect additional links to external resources in the data field.

In the code snippet below we show how your application can send a HealthRecord message with a high priority:

// Obtain an SDK instance.
const bbmsdk = ...;

// Identify the chat
const chatId = "...";

// Create a data object and add the high priority field
const data = {
  highPriority: true
};
const messageSend = {
  tag: 'HealthRecord',
  content: 'The health record content',
  data: data
};

// Send the chat message
bbmsdk.messenger.chatMessageSend(chatId, messageSend);
String chatId = "...";

// Create a data JSONObject and add the high priority field
JSONObject data = new JSONObject();
data.put("highPriority", true);
ChatMessageSend messageSend = new ChatMessageSend(chatId, "HealthRecord")
        .content("The health record content")
        .data(data);
// Send the chat message
BBMEnterprise.getInstance().getBbmdsProtocol().send(messageSend);
// Identify the chat
NSString *chatId = "..."

BBMChatMessageSendMessage *sendMessage = [[BBMChatMessageSendMessage  alloc] initWithChatId:chatId 
                                                                                        tag:@"HealthRecord"];

// Add the highPriority tag as a custom data field
sendMessage.content = @"The health record content";

// Set the rawData to a JSON serializable dictionary
//Alternatively, you may also set the .data field using any object which conforms to BBMJSONMessageSubObject
sendMessage.rawData = @{@"highPriority" :@(YES)};

// Send the message
[[BBMEnterpriseService sharedService] sendMessageToService:sendMessage];
let chatId = "..."

let sendMessage = BBMChatMessageSendMessage(chatId: chatId, tag: "HealthRecord")!
sendMessage.content = "The health record content"

// Set the rawData to a JSON serializable dictionary
// Alternatively, you may also set the .data field using any object which conforms to BBMJSONMessageSubObject
sendMessage.rawData = ["highPriority" : true]

BBMEnterpriseService.shared().sendMessage(toService: sendMessage)
    

See the APIs for sending a message in a chat for Android, iOS, Linux, and JavaScript.

Attachments

Each message in a chat can optionally carry two different kinds of attachments.

Other than its size, the SDK places no restrictions on the content of a thumb or file attachment. It can be any type of text or binary content, and it will not be examined by the SDK.

All attachments are protected using BlackBerry protection mechanisms. Attachments can only be received and read by participants of the chat that have access to the message they are attached to.

Both attachment types preserve their file names (without any directory parts), subject to some portability and security filtering.

See the APIs for sending a message in a chat for Android, iOS, Linux, and JavaScript.

Thumbs

As its name suggests, a thumb attachment is useful for carrying a small thumbnail image, such as the small version of a picture. To use a thumb attachment in this way, your application generates a small image file (such as a JPEG) with a small resolution and reasonable encoding quality.

Messages: Picture

When sending a chat message:

When a chat message is received, the SDK extracts any thumb attachment.

A thumb attachment must be no larger than 56320 bytes (55 KB). Its total size is also subject to a cumulative size limit when combined with the size of the chat message's data (encoded as JSON in UTF-8) and the size of the chat message's content field (in UTF-8). These three fields combined must not exceed 71680 bytes (70 KB).

Files

A file attachment is the recommended way for your application to send large content such as documents, full-sized images, or other custom binary payloads to the participants of a chat. A file attachment must be no larger than 128 MB.

Messages: File

When sending a chat message:

When a chat message is received, the SDK will indicate whether it has a file attachment. If it does, your application can ask the SDK to download it either right away or later.

See the APIs for downloading an attachment for Android, iOS, Linux, and JavaScript.

On Android, iOS, and Linux, your application can ask the SDK to always download newly received file attachments. Your application can allow its users to specify rules for when attachments are downloaded automatically. For example, your application can expose a setting to only download attachments when on Wi-Fi, or when an attachment size is less than a certain threshold.

See the APIs for setting up automatic attachment download for Android, iOS, and Linux.

On Android, iOS, and Linux, both thumb and file attachments can be detached from their chat message in order to free space on an endpoint. When a thumb attachment is detached, it is removed from the endpoint's local storage, and cannot be recovered. When a file attachment is detached, it is removed from the endpoint's local storage. It can be downloaded again until it expires from secure cloud storage.

Detaching an attachment is a local operation that has no effect on other endpoints or participants.

See the APIs for detaching an attachment for Android, iOS, and Linux.

References

Fundamentally, chats in BlackBerry Spark Communications Services consist of a series of chat messages that were sent by the chat's participants. Chat messages appear in a certain order, one after the other, as a sequence.

The Message References feature allows you to move beyond this linear view of the chat history to create richer relationships between chat messages. For example, you can use Message References to implement any or all of the following features.

These are just examples. You can implement many custom behaviours and features using Message References by setting up rich relationships between content.

What Are Message References?

When your application sends a chat message, it can add a ref property that contains a list of outgoing references. Each reference in that list consists of:

Note that the reference's tag here is separate from the chat message's tag. The two strings are not related and do not need to use the same values.

See the APIs for sending a message in a chat for Android, iOS, Linux, and JavaScript.

A source chat message can refer to many different messages as long as each reference has a unique tag value. Each reference is represented by an element in the source chat message's ref list.

The target chat messages referred to by a source chat message's ref list know that they are the target of a reference. These incoming references are indicated on a chat message with the refBy property. On Android, iOS, and Linux, the refBy property contains a list of incoming references, and each incoming reference has:

With the SDK for JavaScript, the refBy property contains a list of incoming references, and each incoming reference has:

Your application can look at a target chat message and instantly determine whether it's been referred to by some later message in the chat, and know the most recent such reference, and whether there are other references between those two messages. This gives you a lot of power to efficiently draw chat messages in very different scenarios, as shown in the examples below.

See the chat message APIs for Android, iOS, Linux, and JavaScript.

On Android, iOS, and Linux, your application can also retrieve the full list of source chat messages that reference the target chat message with a specific tag. See those APIs for Android, iOS, and Linux.

A Simple Example: "Quote"

The values and meanings of the tag strings are controlled by you, the application developer. You can impose any meaning that you want on your tags and the references they create.

For example, say you want to implement chat message quoting. In this feature, when a user responds to a chat message, they can quote the content of that original message. When a chat message is quoted in this way, its display is updated to show the number of times it has been quoted.

Messages: References-Quote

To implement this feature, you decide to use a Message References tag value of Quote to mean that the target chat message has been quoted by the source chat message.

Assume that the user wants to quote the following existing chat message.

Chat Message Field Name Value
messageId 123
content Using references is easy.

Once the user indicates which existing chat message they want to quote, and they've entered the content of the quoting message, your application sends a new chat message that contains a single ref. Assuming that this new chat message ends up with a message ID of 130, it will contain the following fields.

Chat Message Field Name Value
messageId 130
content I agree with this.
ref[0].tag Quote
ref[0].messageId 123

This new quoting chat message is the source chat message in the Message References relationship.

In the code snippet below we show how your application can send a quoting chat message:

// Obtain an SDK instance.
const bbmsdk = ...;

// Identify the quoted message
const quotedMessage = ...;

// Create a new message
const messageSend = {
  tag: 'HealthRecord',
  content: 'The health record content',
  // Add the reference to the quotedMessage
  ref: [{
    tag: 'Quote',
    messageId: quotedMessage.messageId
  }]
};

// Send the chat message
bbmsdk.messenger.chatMessageSend(quotedMessage.chatId, messageSend);
ChatMessage quotedMessage = ...;

// Create a new message
ChatMessageSend messageSend = new ChatMessageSend(quotedMessage.chatId, "Quote")
        .content("I agree with this.");
// Add the reference to the quotedMessage
messageSend.ref(Lists.newArrayList(new ChatMessageSend.Ref(quotedMessage.messageId, ChatMessageSend.Ref.Tag.Quote)));

// Send the new quoting message
BBMEnterprise.getInstance().getBbmdsProtocol().send(messageSend);
// Identify the message we wish to quote
BBMChatMessage *quotedMessage = ...

// quotedMessage.content is "Using references is easy."

BBMChatMessageSendMessage *sendMessage = [[BBMChatMessageSendMessage alloc] initWithChatId:message.chatId 
                                                                                       tag:@"Quote"];

// Create the quote reference
BBMChatMessageSendMessage_Ref *ref = [[BBMChatMessageSendMessage_Ref alloc] initWithMessageId:quotedMessage.messageId 
                                                                                          tag:kBBMChatMessage_Ref_TagQuote];
sendMessage.ref = @[ref];
sendMessage.content = @"I agree with this.";

// Send a new message including a reference to the quoted message
[[BBMEnterpriseService service] sendMessageToService:sendMessage];
// Identify the message we wish to quote
let quotedMessage = ...

//quotedMessage.content is "Using references is easy."

let sendMessage = BBMChatMesageSendMessage(chatId: quotedMessage.chatId, tag: "Quote")

// Create the quote reference
let ref = BBMChatMessageSendMessage_Ref(messageId: quotedMessage.messageId, 
                                              tag:kBBMChatMessage_Ref_TagQuote)!
sendMessage.ref = [ref as Any]
sendMessage.content = "I agree with this."

BBMEnterpriseService.shared().sendMessage(toService: sendMessage)
    

Note that the quoting messages does not contain the content of the message that is being quoted; the messages only reference each other. Both the quoted or quoting message can contain rich content, custom data, thumb or file attachments, and even other references with other tag values. In this example, we presume the new quoting message has only the single outgoing reference, the user's response text, and nothing more, but your application can allow users to quote and respond with any type of content.

On the sending endpoint and on all receiving endpoints for all participants of the chat, the quoted chat message (with ID 123) gains a refBy list. The quoted message now looks like this:

Chat Message Field Name Value
messageId 123
content Using references is easy.
refBy[0].tag Quote
refBy[0].newestRef 130
refBy[0].count 1

This indicates that there is an incoming reference referring to this chat message of type Quote. There is currently only one such reference, and the newest reference is from chat message 130. Note that the quoted message only gains a refBy entry and its other fields did not change.

In both the sender and receiver, you decide how you want to draw these chat messages. In this example, the goal is to show a quote counter on the original chat message and to draw the new quoting message so that it displays the content of both the quoted and quoting message.

To draw the quoted message, your application needs to know how many refBy references there are with the tag Quote. Because refBy is a direct property on the quoted chat messages, your application makes this drawing decision without needing any additional data. When there's an incoming Quote reference, you simply use its count to draw the counter.

To draw the quoting message, your application needs the quoting message's normal content plus the content of the quoted message so it can include both in the display. When a chat message has a ref with a tag of Quote, you know it needs to be drawn as a quoting message. Your application finds the content of the quoted message by asking the SDK for chat message identified by the ref messageId and then uses the content, attachments, and other properties of both messages to draw the quoting message however you want.

In the code snippet below we show how your application can parse a quoting chat message:

// Obtain an SDK instance.
const bbmsdk = ...;

// Identify the message to parse
const message = ...;
let quotedContent;

// Find the quoted message by looking for a message reference with the Quote tag.
quotedMessage = message.ref.find(ref => ref.tag === 'Quote');

// If a message ref with the Quote tag exists, use that message id to retrieve the quoted message.
if(quotedMessage) {
  const quoteMessageId = quotedMessage.messageId;

  const quotedMessageFilter = message => message.messageId.equalTo(quoteMessageId);
  // Retrieve the quoted message
  const quotedMessage = bbmsdk.getChatMessages(message.chatId).find(quotedMessageFilter );

  // quotedContent is "Using references is easy."
  quotedContent = quotedMessage.content;
}
// Identify the message to parse
ChatMessage message = ...;
String quotedContent = "";

// Find the quoted message by looking for a message reference with the Quote tag.
long quotedMessageId = -1;
for (ChatMessage.Ref messageRef : message.ref) {
    if (messageRef.tag.equals(ChatMessage.Ref.Tag.Quote)) {
        quotedMessageId = messageRef.messageId;
    }
}

// If a message ref with the Quote tag exists, use that message ID to retrieve the quoted message
if (quotedMessageId != -1) {
    ChatMessage.ChatMessageKey quotedMessageKey = new ChatMessage.ChatMessageKey(message.chatId, quotedMessageId);
    //Retrieve the quoted message
    ChatMessage quotedMessage = BBMEnterprise.getInstance().getBbmdsProtocol().getChatMessage(quotedMessageKey).get();

    // quotedContent is "Using references is easy."
    quotedContent = quotedMessage.content;
}
// Identify the message to render
BBMChatMessage *message = ...

// message.content is "I agree with this."

// Extract the quote ref it it exists
unsigned long long quotedMessageId = 0;
for(BBMChatMessage_Ref *ref in message.ref) {
  //Messages may have multiple refs, we're interested in the ref with the kBBMChatMessage_Ref_TagQuote tag
  if([ref.tag isEqualToString:kBBMChatMessage_Ref_TagQuote]) {
      quotedMessageId = ref.messageId;
      break;
  }
}

// Look up the quoted message
if(quotedMessageId != 0) {
  BBMChatMessageKey *key = [BBMChatMessageKey keyWithChatId:message.chatId messageId:quotedMessageId];
  BBMChatMessage *quotedMessage = [[BBMEnterpriseService service] model].chatMessage[key];

  // quotedContent is "Using references is easy."
  NSString *quotedContent = quotedMessage.content;
}
// Identify the message to render
let message = ...

// message.content is "I agree with this."

// Look up the quoted message
var quotedMessageId : UInt64 = 0
for case let ref as BBMChatMessage_Ref in message.ref {
  // Messages may have multiple refs, we're interested in the ref with the kBBMChatMessage_Ref_TagQuote tag
  if ref.tag == kBBMChatMessage_Ref_TagQuote {
    quotedMessageId = ref.messageId
    break
  }
}

if(quotedMessageId != 0) {
  let key = BBMChatMessageKey(chatId: message.chatId, messageId: quotedMessageId)!
  let quotedMessage = BBMEnterpriseService.shared().model().chatMessage[key] as! BBMChatMessage

  // quotedContent is "Using references is easy."
  let quotedContent = quotedMessage.content

}
    

As more quoting messages arrive referencing the same quoted message, the SDK increments the count on chat message 123 and reports the resulting property changes to your application. Your application reacts by updating its display of the quoted message and drawing the additional quoting messages in the chat.

Note that the quoting references are created between chat messages sent by different users. The SDK allows cross-user references within the same chat because it enables simple implementations of features like this one.

The SDK comes with a predefined reference tag of Quote to help you build this feature.

See the Rich Chat example application for more information on using Chat Message References to implement message quoting.

A More Involved Example: Message Edits

In this example, suppose that you want to implement an in-place message editing feature. When a user makes a mistake in a chat message, this feature lets them fix it by sending an updated version of their message with the error corrected.

Messages: Edit

This example only considers the simple text content of chat messages, but Message References let you build an editing feature where all application-settable properties of chat messages can be updated. For example, you can use Message References to let the user update a picture that's attached to a chat message.

Presume the user has already sent a chat message with ID 200 with the following relevant chat message fields.

Chat Message Field Value
messageId 200
content References are greet.

Unfortunately, the user made a mistake and misspelled "great" as "greet". Your application lets the user indicate that they want to edit their message and resend a new copy of it, to be displayed in place of the original. To implement this, your application forms a new chat message with the following chat message fields.

Chat Message Field Value
messageId 202
content References are great!
ref[0].tag Edit
ref[0].messageId 200

Remember, the new chat message's messageId field (with value 202) is assigned by the SDK when it is given the message to send. But the messageId field in the first ref element is set by your application to refer to the original chat message that the user wants to edit.

In the sender and all receivers, the SDK will update chat message 200 so that it now has a refBy list. Its chat message fields will now be:

Chat Message Field Value
messageId 200
content References are greet.
refBy[0].tag Edit
refBy[0].newestRef 202
refBy[0].count 1

Note that the content field of the original chat message is unchanged. You haven't lost access to the original text, but, by using the refBy element with the tag Edit, you have gained access to the revised text.

To draw these chat messages, your application notices the Edit reference on the original chat message when the SDK automatically notifies it of a property change. In this feature, the goal is to show the most recent version of the chat message to the user, so your application follows the newestRef on the original message to find the source of the editing reference, chat message 202. That chat message has a content field with the revised text, and your application uses that to draw chat message 200 instead of using the content field in message 200.

Messages Edited

In the code snippet below we show how your application can parse an edited chat message:

// Obtain an SDK instance.
const bbmsdk = ...;

// Identify the message to parse
const message = ...;
let editedContent;

// Find the refBy reference to the new message containing the edits
editedMessage = message.refBy.find(ref => ref.tag === 'Edit');

// If we found an edit refBy then get the editing message
if(editedMessage) {
  const editedMessageId = editedMessage.messageId;

  const editedMessageFilter = message => message.messageId.equalTo(editedMessageId);

  // Retrieve the edited message
  const editedMessage = bbmsdk.getChatMessages(message.chatId).find(editedMessageFilter);

  // editedContent is "References are great!".
  editedContent = editedMessage.content;
}
// Identify the message that has been edited
ChatMessage message = ...;
String editedContent = "";

// Find the refBy reference to the new message containing the edits
long editReferenceId = -1;
for (ChatMessage.RefBy messageRefBy : message.refBy) {
    if (messageRefBy.tag.equals(ChatMessage.RefBy.Tag.Edit)) {
        //Copy the message ID of the edit message that is referencing the original message
        editReferenceId = messageRefBy.newestRef;
    }
}

// If we found an edit refBy then get the editing message
if (editReferenceId != -1) {
    ChatMessage.ChatMessageKey editedMessageKey = new ChatMessage.ChatMessageKey(message.chatId, editReferenceId);
    //Retrieve the editing message
    ChatMessage editingMessage = BBMEnterprise.getInstance().getBbmdsProtocol().getChatMessage(editedMessageKey).get();

    // editedContent is "References are great!"
    editedContent = editingMessage.content;
}
// Identify the original message
BBMChatMessage *originalMessage = ...

// originalMessage.content is "References are greet"

// Look up the edited message
unsigned long long newestEditMessageId = 0;
for(BBMChatMessage_RefBy *ref in originalMessage.refBy) {
  if([ref.tag isEqualToString:kBBMChatMessage_Ref_TagEdit]) {
    newestEditMessageId = ref.newestRef;
    break;
  }
}

if(newestEditMessageId != 0) {
  BBMChatMessageKey *key = [BBMChatMessageKey keyWithChatId:originalMessage.chatId messageId:newestEditMessageId];
  BBMChatMessage *editMessage = [[BBMEnterpriseService service] model].chatMessage[key];

  // editedContent is "References are great!"
  NSString *editedContent = editMessage.content;
}
// Identify the original message
let originalMessage = ...

// originalMessage.content is "References are greet"

// Look up the edited message
var newestEditMessageId : UInt64 = 0
for case let ref as BBMChatMessage_RefBy in message.refBy {
  if ref.tag == kBBMChatMessage_Ref_TagEdit {
    newestEditMessageId = ref.newestRef
    break
  }
}

if(newestEditMessageId != 0) {
  let key = BBMChatMessageKey(chatId: originalMessage.chatId, messageId: newestEditMessageId)!
  let editMessage = BBMEnterpriseService.shared().model().chatMessage[key] as! BBMChatMessage

  // Render the text of the last edit in lieu of the message text.
  // editedContent is "References are great!"
  let editedContent = editMessage.content
}
    

The user can make multiple edits to the chat message. Consider what happens when the user then sends a second edit for the original, such as:

Chat Message Field Value
messageId 203
content References are awesome!
ref[0].tag Edit
ref[0].messageId 200

The SDK automatically updates the properties of the original chat message so that they are:

Chat Message Field Value
messageId 200
content References are greet.
refBy[0].tag Edit
refBy[0].newestRef 203
refBy[0].count 2

Again, the content of chat message 200 is unchanged, but the newestRef still refers to a message that does contain the content that needs to be drawn.

Messages Edited Again

You can decide to hide all the Edit reference source chat messages. Or, you can decide to show them and allow user interaction with them that takes the user back to the original message. Or, you can draw them only if the original chat message is off screen and interacting with them could scroll back to show the edited message.

You can also find all of the edits that have been received for a chat message. This mechanism will be demonstrated in the next example.

Message References give you a lot of flexibility in how you implement and display features because you have all the necessary information about message relationships available, efficiently.

The SDK comes with a predefined reference tag of Edit to help you build this feature.

See the Announcements example application for more information on using Message References to implement message editing.

A Complex Example: Threaded Conversations

The previous examples demonstrated how the scalar properties of each reference on a source or target chat message allow important features to be implemented simply and flexibly. This example demonstrates the full power of Message References by add adding one more piece of information: the list of all source chat messages that refer to a given target chat message.

Suppose you want to allow any arbitrarily chosen message in the chat to become a "parent" chat message and have "child" chat messages be sent and drawn under it. Plus, you want to let any "child" message itself become a "parent" for further nested "child" messages. In other words, you want to allow completely threaded conversations. You can do this with the SDK.

Messages: Threaded

The parent-child relationship is easy to model with the Chat Message References. The parent is the target chat message and the child is the source chat message. You can choose a tag of Thread, for example, and use it to represent these relationships.

A target chat message can have zero or more incoming references, and these correspond to the zero or more children that a parent message can have. A chat message can be both a source and a target message for the same tag. These are all the building blocks necessary to represent the tree of a threaded conversation:

Previous examples have already demonstrated how it is efficient for an application to navigate from children to parents and from parents to their most recent child. But to draw threaded conversations, your application must be able to find all children efficiently.

The SDK offers an efficient API function to navigate from a parent to all of its children (with the same tag). This function takes the tag and messageId of the parent, and returns full information about each of the child messages, no matter how far away in the linear history those messages are.

You can decide how to draw threaded conversations. You might allow only one level of nesting (for example, sidebar conversations). Or, you might allow arbitrary nesting but only draw a single level of children by default, requiring manual user expansion of further nesting levels. Or, you might draw the entire tree of conversations all the time. Or you could do something completely different. Again, the choice is up to you.

The SDK comes with a predefined reference tag of Thread to help you build this feature.

See the Threaded Chat example application for more information on using Message References to build a threaded conversation experience.

Key Takeaways

Destroying Messages

Messages can be destroyed to remove them from receiving endpoints. Or, messages can be privately deleted so that they are no longer visible to the local user's endpoints, but remain visible to everyone else.

Your application can request the private deletion of any chat message. When a chat message is privately deleted, it is removed from:

See the APIs for deleting a message for Android, iOS, Linux, and JavaScript.

Your application can request the permanent destruction of a message that was sent by its own identity. When a chat message is destroyed, it is destroyed from:

Most individual chat message types can be individually destroyed. All message types can be destroyed with a bulk action that acts on every outgoing message from a user in a chat. A user cannot destroy chat messages sent by others.

Messages are destroyed regardless of their delivery state, except that requests to destroy a message in the Failed state will be ignored. There is no guarantee that the recipients have not read the message prior to it being destroyed.

When a message is deleted or destroyed, it's content, attachments, and associated metadata is cleared. However, a record of the message's existence is kept. Within the chat's message history, the message is flagged as either deleted or recalled. This gives your application the choice of showing the end user that a message existed but has since been deleted, or not show the message at all.

Message: Destroyed Message

See the APIs for destroying a message for Android, iOS, Linux, and JavaScript.

On Android, iOS, and Linux, the SDK keeps a persistent cache of the message history within a single chat. Your application can search this message history cache for messages whose content field matches a query string. The search can be scoped to a single chat or you can search across all chats known to an endpoint. When searching all chats, each chat's subject is also searched.

See the APIs for message search for Android, iOS, and Linux.