SimpleChat

This example shows you how to build a basic chat app using the BBM Enterprise SDK. This app demonstrates how easy it can be to integrate messaging into your application. For a more rich chat app experience see the Rich Chat app.

This example builds on the Basic Setup that uses Google OpenID Connect.

Screenshots

image alt preview25 image alt preview25

Features

It allows the user to do the following:

Prerequisites

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

The SimpleChat example uses Google OpenID Connect to obtain a user ID and token that you can use as the AuthToken for the BBM Enterprise SDK. For an overview of Google OpenID Connect, visit https://developers.google.com/identity/sign-in/android/.

To use the SimpleChat example, you must set up a configuration file and a server client ID. For full instructions, visit https://developers.google.com/identity/sign-in/android/start.

You must set up the following elements:

- local keystore file
- client_server_id
- cloud_messaging_sender_id
- your BBM Enterprise SDK user domain.

You can copy these elements from the google-services.json file, and paste them into the app.properties file. The values for these elements are as follows:

- client_server_id = "client_info" : "client_id"
- cloud_messaging_sender_id = "project_info" : "project_number"
- user_domain="your user domain"

Notes:

  1. The Google web documentation contains an error: when creating an OAuth 2.0 client, you also must create an OAuth 2.0 client with the Application Type set to Android. You will need to input the SHA of your own keystore to complete the client ID (see note 2). Once complete, remember to download the google-services.json file again.
  2. You must create your own signing key. The SimpleChat example is setup to use a single signing key for both debug & release. To create your own signing key, visit https://developer.android.com/studio/publish/app-signing.html . The SHA value is required to create an OAuth 2.0 client ID for a mobile device.

This application has been built using gradle 2.14.1 (newer versions have not been validated)

Walkthrough

Follow this guide for a walkthrough showing how the BBM Enterprise SDK is used to demonstrate simple messaging in this sample application.

BBM Enterprise SDK Initialization

To use the BBM Enterprise SDK in our application we need to initialize and start the sdk.

// Initialize BBMEnterprise SDK then start it
BBMEnterprise.getInstance().initialize(this);
BBMEnterprise.getInstance().start();

MainActivity.java

The BBM Enterprise SDK will encrypt our messages for us. However, it's up to the application to provide the persistent key storage and distribution. In this sample application we are using a Firebase database to store and distribute the keys. The specifics of the key distribution are not described in this guide. For more information see BBM Enterprise SDK Protect and check out the example implementation in the BBM SDK Support Library

//Initialize our FirebaseHelper to sync the Protect chat and user keys
FirebaseHelper.initUserDbSyncAndProtected();

Next we need to trigger the Google authentication to fetch a OAuth token. For more information see Identity Management and the Basic Setup Example.

//prompt the user to sign in with their Google account, and pass that data to our user manager when ready
GoogleAuthHelper.initGoogleSignIn(this, FirebaseUserDbSync.getInstance(), getString(R.string.default_web_client_id));

If the identity is currently associated with a different endpoint, we must tell the BBM Enterprise SDK to switch to using this device for our identity. First we add an Observer to the GlobalSetupState ObservableValue. When the GlobalSetupState changes our observers changed() method will be called. If the setup state is DeviceSwitchRequired we will ask the BBM Enterprise SDK to switch to this device by sending a SetupDeviceSwith message.

//Listen to determine if a device switch is necessary.
final ObservableValue<GlobalSetupState> globalSetupState = BBMEnterprise.getInstance().getBbmdsProtocol().getGlobalSetupState();
mDeviceSwitchObserver = new Observer() {
    @Override
    public void changed() {
        if (globalSetupState.get().state == GlobalSetupState.State.DeviceSwitchRequired) {
            //Ask the BBM Enterprise SDK to move the users profile to this device
            BBMEnterprise.getInstance().getBbmdsProtocol().send(new SetupDeviceSwitch());
        }
    }
};
//Add deviceSwitchObserver to the globalSetupStateObservable
globalSetupState.addObserver(mDeviceSwitchObserver);
//Call changed to trigger our observer to run immediately
mDeviceSwitchObserver.changed();

Getting chats

The chats list is provided from the BBM Enterprise SDK as an ObservableList. To track changes to the chat list we register an IncrementalListObserver with the chats list. Our IncrementalListObserver will be informed when the chat list is modified. We pass those change notifications on to a RecyclerView.Adapter which displays the chat list.

//This observer will be used to notify the adapter when chats have been changed or added.
private final IncrementalListObserver mChatListObserver = new IncrementalListObserver() {
    @Override
    public void onItemsInserted(int position, int itemCount) {
        mAdapter.notifyItemRangeInserted(position, itemCount);
    }

    @Override
    public void onItemsRemoved(int position, int itemCount) {
        mAdapter.notifyItemRangeRemoved(position, itemCount);
    }

    @Override
    public void onItemsChanged(int position, int itemCount) {
        mAdapter.notifyItemRangeChanged(position, itemCount);
    }

    @Override
    public void onDataSetChanged() {
        mAdapter.notifyDataSetChanged();
    }
};

//Get the chat list and keep a hard reference to it
mChatList = BBMEnterprise.getInstance().getBbmdsProtocol().getChatList();
//Add our incremental list observer to the chat list
mChatList.addIncrementalListObserver(mChatListObserver);

//Set the adapter in the recyclerview
final RecyclerView chatsRecyclerView = (RecyclerView)findViewById(R.id.chats_list);
chatsRecyclerView.setAdapter(mAdapter);
chatsRecyclerView.setLayoutManager(new LinearLayoutManager(MainActivity.this));

MainActivity.java

To display the chats we are using a simple RecyclerView.Adapter and ViewHolder. For this example each item in the list displays the subject of the chat. When a user clicks on one of the chats we launch the ChatActivity.

//Our chats recycler view adapter
private final RecyclerView.Adapter<ChatViewHolder> mAdapter = new RecyclerView.Adapter<ChatViewHolder>() {
    @Override
    public ChatViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View chatView = LayoutInflater.from(MainActivity.this).inflate(R.layout.chat_item, parent, false);
        return new ChatViewHolder(chatView);
    }

    @Override
    public void onBindViewHolder(ChatViewHolder holder, int position) {
        if (!mChatList.isPending()) {
            holder.title.setText(mChatList.get(position).subject);
            holder.chat = mChatList.get(position);
        }
    }

    @Override
    public int getItemCount() {
        return mChatList.size();
    }
};

//Simple view holder to display a chat
private class ChatViewHolder extends RecyclerView.ViewHolder {

    TextView title;
    Chat chat;

    ChatViewHolder(View itemView) {
        super(itemView);
        title = (TextView)itemView.findViewById(R.id.chat_title);

        //when the chat is clicked open the chat in a new activity
        itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(MainActivity.this, ChatActivity.class);
                intent.putExtra("chat-id",  chat.chatId);
                startActivity(intent);
            }
        });
    }
}

Starting a chat

To start a chat we first need to indentify who we want to chat with. The BBM Enterprise SDK provides a unique id for every user who registers. Your application must maintain a mapping between the identities of your users and the BBM registration id. See the identity management guide for more information.

We display our own BBM registration id at the top of the main activity so we can provide it to another user of this application. Now, when we want to start a chat we can read the registration id from another device running this application. The registration id is a property of user. So first we need to get our own user from the BBM Enterprise SDK. To request a user we need to know the uri for that user. To find our own uri we use the GlobalLocalUri.

final TextView myRegIdTextView = (TextView)findViewById(R.id.my_registration_id);
//Observe the local user to get our registration id
mRegistrationIdObserver = new Observer() {
    @Override
    public void changed() {
        BbmdsProtocol bbmdsProtocol = BBMEnterprise.getInstance().getBbmdsProtocol();
        //Get the uri of the local user first
        GlobalLocalUri localUserUri = bbmdsProtocol.getGlobalLocalUri().get();
        if (localUserUri.getExists() == Existence.YES) {
            //Get the local user and add ourselves as an observer
            ObservableValue<User> localUser = bbmdsProtocol.getUser(localUserUri.value);
            localUser.addObserver(this);
            if (localUser.get().getExists() == Existence.YES) {
                myRegIdTextView.setText(getString(R.string.my_registration_id, localUser.get().regId));
            }
        }
    }
};
BBMEnterprise.getInstance().getBbmdsProtocol().getGlobalLocalUri().addObserver(mRegistrationIdObserver);
mRegistrationIdObserver.changed();

MainActivity.java

This sample adds a menu item which creates a dialog with input fields for a registration id and chat subject. This example only allows creating a chat with a single participant, but the BBM Enterprise SDK supports chats with up to 250 participants. To start a chat we send a ChatStart message to the BBM Enterprise SDK.

//Create a cookie to track the chat creation
final String cookie = UUID.randomUUID().toString();

//Create the invitee using the registration id
ChatStart.Invitees invitee = new ChatStart.Invitees();
invitee.regId(regId);

//Ask the BBM Enterprise SDK to start a new chat with the invitee and subject provided.
BBMEnterprise.getInstance().getBbmdsProtocol().send(new ChatStart(cookie, Lists.newArrayList(invitee), subject));

To track the creation of the chat we need to register a ProtocolMessageConsumer. The ProtocolMessageConsumer is notified of every message that the BBM Enterprise SDK sends to the application. We use the cookie we sent with the ChatStart to process only the response to our request.

If the message was of type ChatStartFailed the BBM Enterprise SDK was not able to start our chat. If the type was listAdd then the BBM Enterprise SDK is returning us the chat that was created. We can parse the chat by using the setAttributes() method. Finally, we launch the chat activity and attach the chatId.

//Add a ProtocolMessageConsumer to track the creation of the chat.
BBMEnterprise.getInstance().getBbmdsProtocolConnector().addMessageConsumer(new ProtocolMessageConsumer() {
    @Override
    public void onMessage(ProtocolMessage message) {
        final JSONObject json = message.getData();
        Logger.d("onMessage: " + message);
        //If the cookie in the incoming message matches the cookie we provided
        //we know this message is the response to our chatStart request.
        if (cookie.equals(json.optString("cookie",""))) {
            //this is for us, stop listening
            BBMEnterprise.getInstance().getBbmdsProtocolConnector().removeMessageConsumer(this);


            if ("chatStartFailed".equals(message.getType())) {
                //If the message type is chatStartFailed the BBM Enterprise SDK was unable to create the chat
                ChatStartFailed chatStartFailed = new ChatStartFailed().setAttributes(message.getJSON());
                Logger.i("Failed to create chat with " + regId);
                Toast.makeText(MainActivity.this, "Failed to create chat for reason " + chatStartFailed.reason.toString(), Toast.LENGTH_LONG).show();
            } else if ("listAdd".equals(message.getType())) {
                //The chat was created successfully
                try {
                    final JSONArray elementsArray = json.getJSONArray("elements");
                    Chat chat = new Chat().setAttributes((JSONObject) elementsArray.get(0));
                    //Start our chat activity
                    Intent intent = new Intent(MainActivity.this, ChatActivity.class);
                    intent.putExtra("chat-id", chat.chatId);
                    startActivity(intent);
                } catch (final JSONException e) {
                    Logger.e(e, "Failed to process start chat message " + message);
                }
            }
        }
    }

    @Override
    public void resync() {
    }
});

Getting chat messages

Now that we've started a chat we need to populate the chat messages. Chat messages can be retrieved from the BBM Enterprise SDK by providing a ChatMessageKey to getChatMessage. The ChatMessageKey is a combination of the chat id and message id and uniquely identifies a chat message. The valid set of message ids for a chat is given by [Chat.lastMessage - Chat.numMessages , Chat.lastMessage).

In this example to load messages we are using ChatMessageList from the BbmSdkSupport module. ChatMessageList is a utility we've created which simplifies lazy loading of chat messages. We use ChatMessageList and the IncrementalListObserver to populate a RecyclerView.Adapter just like we did with the chats list. We do need to tell the ChatMessageList when to start and stop monitoring the chat for new messages. We only monitor messages while the activity is resumed.

//This observer will notify the adapter when chat messages are added or changed.
private IncrementalListObserver mMessageListObserver = new IncrementalListObserver() {
    @Override
    public void onItemsInserted(int position, int count) {
        mAdapter.notifyItemRangeInserted(position, count);
    }

    @Override
    public void onItemsRemoved(int position, int count) {
        mAdapter.notifyItemRangeRemoved(position, count);
    }

    @Override
    public void onItemsChanged(int position, int count) {
        mAdapter.notifyItemRangeChanged(position, count);
    }

    @Override
    public void onDataSetChanged() {
        mAdapter.notifyDataSetChanged();
    }
};

//Create the ChatMessageList
mChatMessageList = new ChatMessageList(mChatId);

//Initialize the recycler view
final RecyclerView messageRecyclerView = (RecyclerView) findViewById(R.id.messages_list);
messageRecyclerView.setAdapter(mAdapter);
LinearLayoutManager layoutManager = new LinearLayoutManager(ChatActivity.this);
messageRecyclerView.setLayoutManager(layoutManager);

//Add our IncrementalListObserver to the ChatMessageList
mChatMessageList.addIncrementalListObserver(mMessageListObserver);

@Override
protected void onPause() {
    super.onPause();
    //Stop loading messages from the ChatMessageList
    mChatMessageList.stop();
    //Mark all the messages as read when closing the chat
    markMessagesAsRead();
}

@Override
protected void onResume() {
    super.onResume();
    //Start loading messages from the ChatMessageList
    mChatMessageList.start();
    //Mark all the messages as read when opening the chat
    markMessagesAsRead();
}

ChatActivity.java

Displaying chat messages

Our ChatMessages are text only in this example. But ChatMessage also supports file attachments, thumbnails, recall and custom data and types you can define.

To display the chat messages we provide our Adapter and ViewHolder. To left and right align outgoing and incoming messages we're providing two different types in our Adapter. Each chat message includes a set of flags we can check to find out if the message was incoming.

@Override
public int getItemViewType(int position) {
    //Use the ChatMessage.Flag to determine if the message is incoming or outgoing and use the correct type
    ChatMessage message = mChatMessageList.get(position);
    return message.hasFlag(ChatMessage.Flags.Incoming) ? TYPE_INCOMING : TYPE_OUTGOING;
}

@Override
public MessageViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    //The incoming message layout is right justified, outgoing left justified
    int layoutRes = viewType == TYPE_INCOMING ? R.layout.incoming_message_item : R.layout.outgoing_message_item;
    View chatView = LayoutInflater.from(ChatActivity.this).inflate(layoutRes, parent, false);
    return new MessageViewHolder(chatView);
}

ChatActivity.java

Messages have a state which is used to notify users if their message has been sent, delivered or read. Incoming messages that are unread are bolded and outgoing messages have their state prepended. Finally, we add the content of the text message, or if the message not a Text message then display the name of the message type.

@Override
public void onBindViewHolder(MessageViewHolder holder, int position) {
    //Get the message to display
    ChatMessage message = mChatMessageList.get(position);
    if (message.getExists() == Existence.MAYBE) {
        return;
    }
    String prefix = "";
    if (message.hasFlag(ChatMessage.Flags.Incoming)) {
        if (message.state != ChatMessage.State.Read) {
            //instead of displaying sent status like BBM, just show bold until it is read
            holder.messageText.setTypeface(null, Typeface.BOLD);
        } else {
            holder.messageText.setTypeface(null, Typeface.NORMAL);
        }
    } else {
        //show the state of the outgoing message
        switch (message.state) {
            case Sending:
                prefix = "(...) ";
                break;
            case Sent:
                prefix = "(S) ";
                break;
            case Delivered:
                prefix = "(D) ";
                break;
            case Read:
                prefix = "(R) ";
                break;
            case Failed:
                prefix = "(F) ";
                break;
            default:
                prefix = "(?) ";
        }
    }
    if (message.tag.equals(ChatMessage.Tag.Text)) {
        holder.messageText.setText(prefix + message.content);
    } else {
        //For non-text messages just display the message type Tag
        holder.messageText.setText(message.tag);
    }
}

Sending a chat message

Sending a new message in the chat is easy. We create a ChatMessageSend and the text the user typed as the content. You can also set a file, thumbnail and custom JSON data to the chat message.

sendButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        String text = inputText.getText().toString();
        if (!text.isEmpty()) {
            //Send a new outgoing text message, setting the content to the input text
            BBMEnterprise.getInstance().getBbmdsProtocol().send(new ChatMessageSend(mChatId, ChatMessageSend.Tag.Text).content(text));
            inputText.setText("");
        }
    }
});

ChatActivity.java

Marking messages as read

We need to notify the BBM Enterprise SDK of when a user has read a message. The BBM Enterprise SDK will propogate the status change to the other participants in the chat. To mark a message as read we send a MessageStatus to the BBM Enterprise SDK. All messages which are older than the message id provided are automatically marked as read. To keep this example simple, we are just sending a MessageStatus change to type READ with the lastMessage id. This example only sends message status changes when the chat activity is paused or resumed. You might want to choose a different action, like a marking a message as read when it becomes visible in the chat.

/**
 * Mark all messages in the chat as read
 */
private void markMessagesAsRead() {
    final ObservableValue<Chat> obsChat = BBMEnterprise.getInstance().getBbmdsProtocol().getChat(mChatId);
    mMarkMessagesReadObserver = new Observer() {
        @Override
        public void changed() {
            final Chat chat = obsChat.get();
            if (chat.exists == Existence.YES) {
                //remove ourselves as an observer so we don't get triggered again
                obsChat.removeObserver(this);
                if (chat.numMessages == 0 || chat.lastMessage == 0) {
                    return;
                }

                String chatUri = "bbmpim://chat/" + mChatId;
                //Ask the BBM Enterprise SDK to mark the last message in the chat as read.
                //All messages older then this will be marked as read.
                BBMEnterprise.getInstance().getBbmdsProtocol().send(
                        new MessageStatus(chatUri, chat.lastMessage, MessageStatus.Status.Read)
                );
            }
        }
    };
    //Add the chatObserver to the chat
    obsChat.addObserver(mMarkMessagesReadObserver);
    //Run the changed method
    mMarkMessagesReadObserver.changed();
}

ChatActivity.java