The Announcements example application demonstrates how a user can post announcements as custom messages in a chat. We also demonstrate how the Chat Message References feature of BlackBerry Spark Communications Services can be used to edit an announcement and view the edit history. This example builds on SimpleChat and utilizes the Support library library to quickly create a basic chat application. This example also uses the Android RecyclerView.
- Post announcements as custom messages in a chat
- View all announcements posted in a chat
- Edit an announcement
- View the edit history of an announcement
This example requires the Spark Communications SDK, which you can find along with related resources at the locations below.
- Instructions to Download and Configure the SDK.
- Android Getting Started instructions in the Developer Guide.
- API Reference
Visit the Getting Started with Android section to see the minimum requirements.
Getting started video
By default, this example application is configured to work in a domain with user authentication disabled and the BlackBerry Key Management Service enabled. See the Download & Configure section of the Developer Guide to get started configuring a domain in the sandbox.
Once you have a domain in the sandbox, edit Announcment's app.properties
file
to configure the example with your domain ID.
# Your Spark Domain ID
user_domain="your_spark_domain"
When you run the Announcements application, it will prompt you for a user ID and a password. Since you've configured your domain to have user authentication disabled, you can enter any string you like for the user ID and an SDK identity will be created for it. Other applications that you run in the same domain will be able to find this identity by this user ID. The password is used to protected the keys stored in the BlackBerry Key Management Service.
Notes:
- To complete a release build you must create your own signing key. To create your own signing key, visit https://developer.android.com/studio/publish/app-signing.html.
- After creating your signing key set the key store password, key password, key alias and path to the keystore file in the 'app.properties' file.
- This application has been built using gradle 6.1.1 (newer versions have not been validated).
- Starting a chat
- Setup of Chat Activity
- Adding a user to a chat
- Create an Announcement message
- Editing an Announcement message
- View all the sent Announcement
- View history of the Announcement
- Marking messages as read
- End Chat
The Support library
provides a helper to start a new chat. A regId
is needed to start a chat as demonstrated in the
SimpleChat. Instead of creating a
ProtocolMessageConsumer
and implementing the code to find and verify a message, that code has been
rolled into the ChatStartHelper class. To start a chat you need to provide a list
containing one or more regIds
, chat subject, and a callback.
ChatStartHelper.startNewChat(new long[]{regId}, subject, new ChatStartHelper.ChatStartedCallback() {
@Override
public void onChatStarted(@NonNull String chatId) {
//Start our chat activity
Intent intent = new Intent(MainActivity.this, ChatActivity.class);
intent.putExtra(ChatActivity.INTENT_EXTRA_CHAT_ID, chatId);
startActivity(intent);
}
@Override
public void onChatStartFailed(ChatStartFailed.Reason reason) {
Logger.i("Failed to create chat with " + regId);
Toast.makeText(MainActivity.this, "Failed to create chat for reason " + reason.toString(), Toast.LENGTH_LONG).show();
}
});
The
Support library
also offers some additional helpers to quickly create chat
activity, see chatActivity.java
. It also provides an extensible
ChatMessageRecyclerViewAdapter
to create message bubbles. The
ChatMessageRecyclerViewAdapter
ties a ChatBubbleColorProvider
and
ChatMessageViewProvider
together to automatically create the chat message
bubbles for you. Start by creating a ChatMessageRecyclerViewAdapter and setting
it in the
Recycler View.
// Initialize the recycler view
final RecyclerView recyclerView = findViewById(R.id.messages_list);
mAdapter = new ChatMessageRecyclerViewAdapter(this,
recyclerView,
mChatId,
new MessageViewProvider(), // our extend ChatMessageRecyclerViewAdapter
new MessageColorProvider()); // our extend ChatBubbleColorProvider
// Set the adapter to auto-scroll on new items
mAdapter.setAutoScrollOnNewItem(true);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(ChatActivity.this);
linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setAdapter(mAdapter);
The MessageViewProvider
creates the different view types we want to have in the chat activity. You can define the message types you want to render with a simple integer definition. For example, we define an outgoing, incoming, chat event, and other bubble types (to be added later in this example).
public final class MessageViewProvider implements ChatMessageViewProvider {
static final int ITEM_VIEW_TYPE_TEXT_OUTGOING = 0;
static final int ITEM_VIEW_TYPE_TEXT_INCOMING = 1;
static final int ITEM_VIEW_TYPE_CHAT_EVENT = 2;
static final int ITEM_VIEW_TYPE_OTHER = 99;
...
The values are used to determine what view type will be used for each ChatMessage. The view type is decided in the method getItemTypeForMessage()
.
@Override
public int getItemTypeForMessage(ChatMessage item) {
if (item.exists != Existence.YES) {
return ITEM_VIEW_TYPE_OTHER;
}
// Next look at the item.tag, handle the SDK supported tags and
// look for the custom Announcement tag.
switch (item.tag) {
case ChatMessage.Tag.Join:
case ChatMessage.Tag.Leave:
case ChatMessage.Tag.Subject:
return ITEM_VIEW_TYPE_CHAT_EVENT;
case ChatMessage.Tag.Text:
boolean isIncoming = item.hasFlag(ChatMessage.Flags.Incoming);
return isIncoming ? ITEM_VIEW_TYPE_TEXT_INCOMING : ITEM_VIEW_TYPE_TEXT_OUTGOING;
}
return ITEM_VIEW_TYPE_OTHER;
}
Once the view type is determined, the specific view holder can be created. The custom bubble layouts included in this example are:
- EventHolder
- IncomingTextHolder
- OutgoingTextHolder
- UnknownMessage
These holders have their own xml layouts, colors and strings that will be used in the app. When the RecyclerView is ready to render the view, the appropriate holder will be created based on the view type.
@Override
public RecyclerViewHolder<DecoratedMessage> onCreateRecyclerViewHolder(ViewGroup viewGroup, int viewType) {
switch (viewType) {
case ITEM_VIEW_TYPE_TEXT_OUTGOING:
return new OutgoingTextHolder(getLayoutInflater(), viewGroup);
case ITEM_VIEW_TYPE_TEXT_INCOMING:
return new IncomingTextHolder(getLayoutInflater(), viewGroup);
case ITEM_VIEW_TYPE_CHAT_EVENT:
return new EventHolder(getLayoutInflater(), viewGroup);
case ITEM_VIEW_TYPE_OTHER:
default:
return new UnknownMessage(getLayoutInflater(), viewGroup);
}
}
The next piece is the MessageColorProvider
. This implements the
ChatBubbleColorProvider
which provides the basic chat bubble shape and color
to be used for each chat message. The important items for this example are the
R.drawable and the first R.color item, these are tied together in the drawable
xml. For this example we defined:
public final class MessageColorProvider implements ChatBubbleColorProvider {
private final ChatBubbleColors mIncoming;
private final ChatBubbleColors mOutgoing;
MessageColorProvider() {
mIncoming = new ChatBubbleColors(ChatActivity.this,
R.drawable.incoming_message_background,
R.color.chat_bubble_incoming,
R.color.chat_bubble_text_incoming,
R.color.chat_bubble_text_incoming,
R.color.chat_bubble_highlight_outgoing,
R.color.chat_bubble_status_outgoing,
R.color.chat_bubble_error_outgoing,
R.color.chat_bubble_alert_text_outgoing);
mOutgoing = new ChatBubbleColors(ChatActivity.this,
R.drawable.outgoing_message_background,
R.color.chat_bubble_outgoing,
R.color.chat_bubble_text_incoming,
R.color.chat_bubble_text_incoming,
R.color.chat_bubble_highlight_outgoing,
R.color.chat_bubble_status_outgoing,
R.color.chat_bubble_error_outgoing,
R.color.chat_bubble_alert_text_outgoing);
}
...
The SDK supports chats with up to 250 participants. We created this chat with only one other user, so we need a mechanism to add more users to the existing chat.
Next we need to handle the menu action
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item == null) {
return false;
}
switch (item.getItemId()) {
...
case R.id.addUserMenu:
showAddUserDialog();
return true;
}
return super.onOptionsItemSelected(item);
}
When the user selects "Add User" our method showAddUserDialog()
is called. The method will show an AlertDialog that will allow a user to enter in a Spark Registration ID. On the positive button action the following is sent to trigger the SDK to add the provided user:
final Long regId = Long.valueOf(regIdText.getText().toString());
final ChatInvite.Invitees invite = new ChatInvite.Invitees();
invite.regId(regId);
final ChatInvite message = new ChatInvite(mChatId, Collections.singletonList(invite));
BBMEnterprise.getInstance().getBbmdsProtocol().send(message);
The first step in creating a custom text message is to define the tag that represents this ChatMessage.
// The custom tag used to identify an announcement message
public final static String TAG_ANNOUNCEMENTS = "announcement";
In chatActivity.java
we have defined a button dedicated for creating an
Announcement message. To send the Announcement message we create a new
ChatMessageSend
providing the chat identifier and our defined message tag TAG_ANNOUNCEMENTS and
the text content of the message.
final AlertDialog.Builder builder = new AlertDialog.Builder(ChatActivity.this, R.style.AppTheme_dialog)
.setMessage(R.string.announcement_confirm)
.setCancelable(false)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
final ChatMessageSend message = new ChatMessageSend(mChatId, TAG_ANNOUNCEMENTS)
.content(text);
BBMEnterprise.getInstance().getBbmdsProtocol().send(message);
inputText.setText("");
}
})
.setNegativeButton(android.R.string.no, null);
Now that an Announcement has been created, we need to display the message and
have it standout from regular chat messages. This involves updating the
MessageViewProvider
to add a new view type and then getItemTypeForMessage
to
return a new holder. To do this, we add in the ITEM_VIEW_TYPE_ANNOUNCEMENT
type and a new holder AnnouncementHolder
public final class MessageViewProvider implements ChatMessageViewProvider {
static final int ITEM_VIEW_TYPE_TEXT_OUTGOING = 0;
static final int ITEM_VIEW_TYPE_TEXT_INCOMING = 1;
static final int ITEM_VIEW_TYPE_CHAT_EVENT = 2;
static final int ITEM_VIEW_TYPE_ANNOUNCEMENT = 3;
static final int ITEM_VIEW_TYPE_OTHER = 99;
@Override
public RecyclerViewHolder<DecoratedMessage> onCreateRecyclerViewHolder(ViewGroup viewGroup, int viewType) {
switch (viewType) {
...
case ITEM_VIEW_TYPE_ANNOUNCEMENT:
return new AnnouncementHolder(getLayoutInflater(), viewGroup);
...
}
}
@Override
public int getItemTypeForMessage(ChatMessage item) {
...
// Next look at the item.tag, handle the SDK supported tags and
// look for the custom Announcement tag.
switch (item.tag) {
...
case TAG_ANNOUNCEMENTS:
return ITEM_VIEW_TYPE_ANNOUNCEMENT;
}
return ITEM_VIEW_TYPE_OTHER;
}
We also define a new color in MessageColorProvider
to be used for our announcement chat bubble.
MessageColorProvider() {
mIncoming = new ChatBubbleColors(ChatActivity.this,
R.drawable.incoming_message_background,
R.color.chat_bubble_incoming,
R.color.chat_bubble_text_incoming,
R.color.chat_bubble_text_incoming,
R.color.chat_bubble_highlight_outgoing,
R.color.chat_bubble_status_outgoing,
R.color.chat_bubble_error_outgoing,
R.color.chat_bubble_alert_text_outgoing);
mOutgoing = new ChatBubbleColors(ChatActivity.this,
R.drawable.outgoing_message_background,
R.color.chat_bubble_outgoing,
R.color.chat_bubble_text_incoming,
R.color.chat_bubble_text_incoming,
R.color.chat_bubble_highlight_outgoing,
R.color.chat_bubble_status_outgoing,
R.color.chat_bubble_error_outgoing,
R.color.chat_bubble_alert_text_outgoing);
mAnnouncement = new ChatBubbleColors(ChatActivity.this,
R.drawable.announcement_message_background,
R.color.announcement_background,
R.color.chat_bubble_text_incoming,
R.color.chat_bubble_text_incoming,
R.color.chat_bubble_highlight_outgoing,
R.color.chat_bubble_status_outgoing,
R.color.chat_bubble_error_outgoing,
R.color.chat_bubble_alert_text_outgoing);
}
Finally we define our announcement bubble, AnnouncementHolder.java
as
public final class AnnouncementHolder extends BaseBubbleHolder implements RecyclerViewHolder<DecoratedMessage> {
private TextView mMessageBody;
private View mContextView;
public AnnouncementHolder(LayoutInflater layoutInflater, ViewGroup parent) {
super(layoutInflater, parent, com.bbm.sdk.support.R.layout.chat_bubble_base_outgoing_holder);
}
@Override
public View createView(LayoutInflater inflater, ViewGroup parent) {
mContextView = super.setContentSpecificView(inflater, R.layout.chat_bubble_announcement);
mMessageBody = mContextView.findViewById(R.id.message_body);
return super.getRootView();
}
@Override
public void updateView(DecoratedMessage decoratedMessage, int position) {
super.updateGeneric(decoratedMessage);
ChatMessage message = decoratedMessage.getChatMessage();
if (message != null) {
mMessageBody.setText(Helper.getMessageContent(message));
}
}
@Override
public void onRecycled() {
mMessageBody.setText(null);
mContextView = null;
}
}
After this is done, all announcement chat bubbles will have their own look compared to other regular text chat messages.
Of note here, the AnnouncementHolder
is setup to show the most recent edited announcement, this is achieved by using the Helper.getMessageContent()
this is be covered in more detail Editing an Announcement message.
Now, lets add the ability to edit the announcement. To do so, add in the support
to long-click the announcement chat bubble to bring up a popup-menu in our chat
activity. We first need to update our AnnouncementHolder
to implement the
[Support library]Support library
ContextMenuAwareHolder
interface and the required method. The
result is a RecyclerContextMenuInfoWrapperView
is created internally in the
[Support library which]Support library
will be used to link the AnnouncementHolder
to Androids menu framework.
public final class AnnouncementHolder extends BaseBubbleHolder implements RecyclerViewHolder<DecoratedMessage>, ContextMenuAwareHolder {
...
@Override
public List<View> getContextMenuAwareView() {
return Collections.singletonList(mContextView);
}
}
Next update the chat activity to implement the Android Activity method onCreateContextMenu
. The chat activity will need to keep track of which user selected announcement message has been long-pressed in mAnnouncementMessageToEdit. To determine that the code must verify the provided menuInfo is an instance of our RecyclerContextMenuInfoWrapperView
inner class of RecyclerContextMenuInfo
. The inner class contains useful information to find a chat message from the ChatMessageRecyclerViewAdapter. From there we simply look for our announcement message tag to set the the mAnnouncementMessageToEdit.
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
if (menuInfo instanceof RecyclerContextMenuInfoWrapperView.RecyclerContextMenuInfo) {
RecyclerContextMenuInfoWrapperView.RecyclerContextMenuInfo info =
(RecyclerContextMenuInfoWrapperView.RecyclerContextMenuInfo) menuInfo;
int position = info.position;
DecoratedMessage decoratedMessage = mAdapter.getItem(position);
ChatMessage chatMessage = decoratedMessage.getChatMessage();
if (chatMessage.tag.equals(TAG_ANNOUNCEMENTS)) {
menu.add(Menu.NONE, MENU_ANNOUNCEMENT_ID, Menu.NONE, R.string.edit_announcement);
mAnnouncementMessageToEdit = decoratedMessage;
} else {
mAnnouncementMessageToEdit = null;
}
}
}
(where R.string.edit_announcement displays "Edit Announcement")
When a user does click on the popup menu item, mAnnouncementMessageToEdit
will have the selected chat message when onContextItemSelected
is invoked.
@Override
public boolean onContextItemSelected(MenuItem item) {
if (item.getItemId() == MENU_ANNOUNCEMENT_ID && mAnnouncementMessageToEdit != null) {
Helper.showEditAnnouncementDialog(ChatActivity.this, mAnnouncementMessageToEdit.getChatMessage());
return true;
}
return super.onContextItemSelected(item);
}
At this point the Helper.showEditAnnouncementDialog
will show a dialog that allows a user to edit the announcement message. ChatMessageSend is used to first construct a the message, setting the chat ID and ChatMessageSend.Tag value of Text
. If we send the message, it would be considered a regular text message, we need to link this new message as an edit to the announcement.
To do so, we need to use the ChatMessageSend.Ref to set the original announcement chat message identifier and the type of reference using ChatMessageSend.Ref.Tag. It is important to note a single message can only have one reference for each ChatMessageSend.Ref.Tag, this is done to allow multiple different kinds of references between messages. For example, a message could both 'Quote' one message and 'Edit' another. Once the ChatMessageSend.Ref is set we can send the message as normally.
...
// 1. Create the new message to be sent
final ChatMessageSend editedMessage = new ChatMessageSend(announcementMessage.chatId, ChatMessageSend.Tag.Text)
.content(editText.getText().toString().trim());
// 2. Set the reference of the editedMessage to the announcement message identifier and set the reference tag to "edit"
editedMessage.ref(Collections.singletonList(new ChatMessageSend.Ref(announcementMessage.messageId, ChatMessageSend.Ref.Tag.Edit)));
// 3. Send the message
BBMEnterprise.getInstance().getBbmdsProtocol().send(editedMessage);
Backing up for a moment, when a user decides to edit the announcement we want to display the most recent announcement edit. To find the most recent message we need to traverse the ChatMessage.RefBy array looking for a ChatMessage.RefBy.Tag of type Edit
. The ChatMessage.RefBy will also contain the message identifier to the newest message. With that data we can lookup the referenced chat message and obtain the message content to display. This is all wrapped up in the Helper.getMessageContent()
method.
public static String getMessageContent(ChatMessage message) {
for (ChatMessage.RefBy ref : message.refBy) {
if (ref.tag == ChatMessage.RefBy.Tag.Edit && ref.newestRef != 0) {
//We only care about the most recent edit reference to this message
ChatMessage.ChatMessageKey key = new ChatMessage.ChatMessageKey(message.chatId, ref.newestRef);
ChatMessage refedByMessage = BBMEnterprise.getInstance().getBbmdsProtocol().getChatMessage(key).get();
if (refedByMessage.exists == Existence.YES) {
return refedByMessage.content;
}
}
}
return message.content;
}
Putting it together, when we need to show the most recent announcement edit, simply call the Helper.getMessageContent()
// Get the announcement message, we want to update the latest edit, so need to find the
// appropriate message
editText.setText(getMessageContent(announcementMessage));
At this point we also want to visually show an edited announcement message differently from a regular chat message and announcement message. Similar to Create an Announcement message we can add a new view type and supporting holders. This will show up as below:
This example has an activity to view all the announcements in a chat,
ViewAnnouncementsActivity.java
. The ViewAnnouncementsActivity
uses the
Android RecyclerView
and the [Support library]Support library
MonitoredRecyclerAdapter
to create a data adapter. We can then use
ChatMessageCriteria
to request a filter list of items from the SDK. To do so simply provide
the chat identifier and the custom announcement tag TAG_ANNOUNCEMENTS
AnnouncementsDataAdapter(@NonNull final Context context, @NonNull final RecyclerView recyclerView) {
super(context, recyclerView);
// Create a lookup criteria. We need to supply the ChatId and our custom Announcement Tag
final ChatMessageCriteria criteria = new ChatMessageCriteria()
.chatId(mChatId)
.tag(ChatActivity.TAG_ANNOUNCEMENTS);
// Query BBM Enterprise SDK for the data.
mList = BBMEnterprise.getInstance().getBbmdsProtocol().getChatMessageList(criteria);
}
In addition to viewing a list of announcements in a chat we can also see all the edits to single announcement. The activity ViewAnnouncementHistory
is similar to the View all the sent Announcement, but the usage of the ChatMessageCriteria differs.
Since we want to show all the edits to a single announcement we must create a ChatMessage.Ref that uses the announcement message identifier as the reference to filter by. In addition we need to set the ChatMessage.Ref.Tag to Edit
. Once created the ChatMessage.Ref is used in the ChatMessageCriteria along with the chat identifier. This however returns a list of edited announcements, which will not include the original announcement item, to add that into the list, we request that chat message separately as the chat and message identifier are known.
// Get the root message for the history. It is not included in the edit lookup.
mRootMessage = BBMEnterprise.getInstance().getBbmdsProtocol().getChatMessage(
new ChatMessage.ChatMessageKey(mChatId, mMessageId));
// First create a reference query object. In this case we want all history of
// the given mMessageId that is of type ChatMessage.Ref.Tag.Edit
ChatMessage.Ref ref = new ChatMessage.Ref();
ref.messageId = mMessageId;
ref.tag = ChatMessage.Ref.Tag.Edit;
// Next create the lookup criteria object, pass in the ChatId associated with the
// messageId, our custom Announcement Tag and the ChatMessage.Ref created above.
final ChatMessageCriteria criteria = new ChatMessageCriteria()
.chatId(mChatId)
.ref(ref);
// Now query the BBM Enterprise SDK for data.
mList = BBMEnterprise.getInstance().getBbmdsProtocol().getChatMessageList(criteria);
These samples are released as Open Source and licensed under the Apache 2.0 License.
The Android robot is reproduced or modified from work created and shared by Google and used according to terms described in the Creative Commons 3.0 Attribution License.
This page includes icons from: https://material.io/icons/ used under the Apache 2.0 License.
If you find an issue in one of the Samples or have a Feature Request, simply file an issue.