Skip to content
This repository has been archived by the owner on Jan 11, 2024. It is now read-only.

Latest commit

 

History

History
638 lines (495 loc) · 32.1 KB

File metadata and controls

638 lines (495 loc) · 32.1 KB

BlackBerry Spark Communications Services

Announcements for Android

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.

Features

  • 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

Getting Started

This example requires the Spark Communications SDK, which you can find along with related resources at the locations below.

Visit the Getting Started with Android section to see the minimum requirements.

YouTube Getting Started Video

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).

Walkthrough

Starting a 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();
        }
    });

Setup of Chat Activity

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);
        }
        ...

Adding a user to a chat

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);

Create an Announcement 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.

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:


View all the sent Announcement

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);
    }

View history of the Announcement

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);

License

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.

Reporting Issues and Feature Requests

If you find an issue in one of the Samples or have a Feature Request, simply file an issue.