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

Latest commit

 

History

History
602 lines (492 loc) · 27.8 KB

File metadata and controls

602 lines (492 loc) · 27.8 KB

BlackBerry Spark Communications Services

SimpleChat for Android

The Simple Chat example demonstrates how you can build a simple chat application using BlackBerry Spark Communications Services. This example demonstrates how easily messaging can be integrated into your application. For a more rich chat app experience please see the Rich Chat app provided with the SDK. This example builds on the Quick Start example that demonstrates how you can authenticate with the SDK using the Identity Provider of your application.

Features

It allows the user to do the following:

  • Create a chat
  • View the chat list
  • View all sent and received messages in a chat
  • Send text-based messages
  • Mark incoming messages as Read

Getting Started

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

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 Simple Chat'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 Simple Chat 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

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

SDK Initialization

To use the BlackBerry Spark Communications Services SDK in your application you need to initialize and start the sdk.

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

MainActivity.java

The SDK will encrypt your messages for you. The SimpleChat example uses the BlackBerry Key Management System to store the security keys generated by the SDK. The specifics of the key distribution are not described in this guide. For more information visit the Protect guide and check out the example implementation in the Support library.

KeySource keySource = new BlackBerryKMSSource(new UserChallengePasscodeProvider(context));
KeySourceManager.setKeySource(keySource);
keySource.start();

This sample uses a mock identity provider to create unsigned JWT tokens. For more information see Identity Management and the Quick Start sample. The MockTokenProvider class from the support library is used to generate the tokens.

We monitor the GlobalSetupState to handle key states. 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 SDK to switch to this device by sending a SetupRetry message.
  • NotRequested we will register the local device by sending a EndpointUpdate.
  • Full we will need to deregister a device using EndpointDeregister. If successful then send a SetupRetry message to continue setup.
//Listen to the setup events
final ObservableValue<GlobalSetupState> globalSetupState = BBMEnterprise.getInstance().getBbmdsProtocol().getGlobalSetupState();
mBbmSetupObserver = new Observer() {
    @Override
    public void changed() {
        final ObservableValue<GlobalSetupState> globalSetupState = BBMEnterprise.getInstance().getBbmdsProtocol().getGlobalSetupState();

        if (globalSetupState.get().exists != Existence.YES) {
            return;
        }

        GlobalSetupState currentState = globalSetupState.get();

        switch (currentState.state) {
            case NotRequested:
                SetupHelper.registerDevice("Simple Chat", "Simple Chat example");
                break;
            case DeviceSwitchRequired:
                //Ask the BBM Enterprise SDK to move the users profile to this device
                BBMEnterprise.getInstance().getBbmdsProtocol().send(new SetupRetry());
                break;
            case Full:
                SetupHelper.handleFullState();
                break;
            case Ongoing:
            case Success:
            case Unspecified:
                break;
        }
    }
};

//Add setup observer to the globalSetupStateObservable
globalSetupState.addObserver(mBbmSetupObserver);

//Call changed to trigger our observer to run immediately
mBbmSetupObserver.changed();

Getting chats

The chatList is provided from the 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 you first need to indentify who you want to chat with. The SDK provides a unique regId for every user who registers. The SDK keeps a mapping of your application user identifier and regIds. See the identity management guide for more information.

This example adds a menu item which creates a dialog with input fields for a user id and chat subject. The regId for a user can be found using the IdentitiesGet message. We are using the UserIdentityMapper class from the support library to get an Observable map result. If the map result is found we can use the regId to start the chat.

SingleshotMonitor.run(new SingleshotMonitor.RunUntilTrue() {
    @Override
    public boolean run() {
        //Lookup the Spark registration id for the provided user id
        UserIdentityMapper.IdentityMapResult mapResult =
                UserIdentityMapper.getInstance().getRegIdForUid(userId, true).get();
        if (mapResult.existence == Existence.MAYBE) {
             return false;
        }
        if (mapResult.existence == Existence.YES) {
            String subject = TextUtils.isEmpty(secondaryInput) ? userId : secondaryInput;
            startChat(mapResult.regId, subject);
        } else {
            Toast.makeText(MainActivity.this, getString(R.string.user_id_not_found, userId), Toast.LENGTH_LONG).show();
        }

        return false;
    }
});

This example only allows creating a chat with a single participant, but the SDK supports chats with up to 250 participants. To start a chat we send a ChatStart message to the 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 you need to register a ProtocolMessageConsumer. The ProtocolMessageConsumer is notified of every message that the SDK sends to the application. Use the cookie we sent with the ChatStart to process only the response to your request.

If the message was of type ChatStartFailed the SDK was not able to start your chat. If the type was listAdd then the SDK is returning us the chat that was created. You can parse the chat by using the setAttributes() method. Finally, 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 you have started a chat you need to populate the chat messages. Chat messages can be retrieved from the 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].

In this example to load messages we are using ChatMessageList from the Support library library. 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

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

You need to notify the SDK of when a user has read a message. The SDK will propagate the status change to the other participants in the chat. To mark a message as read, send a ChatMessageRead to the 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 ChatMessageRead change 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;
                }

                //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 ChatMessageRead(mChatId, chat.lastMessage)
                );
            }
        }
    };
    //Add the chatObserver to the chat
    obsChat.addObserver(mMarkMessagesReadObserver);
    //Run the changed method
    mMarkMessagesReadObserver.changed();
}

ChatActivity.java

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.