The rest of the walkthrough below presumes that you have the application running in your local environment. If you haven't done so yet, follow the instructions in the previous section.
In this tutorial, you'll explore Animal Forest Chat, a simple, yet feature-rich Android chat app with Java and PubNub. Your app will run on Android Phone, using PubNub as the messaging infrastructure and framework to connect your chat users.
The Android app doesn't use any specific architecture pattern. Instead, it uses a classic
Activity <–> Fragment communication via callbacks. The main application components are:
MainActivitytakes care of managing
PubNub, and updates any parts of the UI not handled by the fragments.
ChatFragmentdisplays the chat UI (list of messages, channel history, and sending new messages).
ChatInfoFragmentdisplays the users currently online in the channel, and also reacts to presence changes (
Historyis a helper class that fetches messages from the channel, converts them into plain Java objects, and groups and formats them, all in a paginated manner.
ChatAdapteris the actual
RecyclerView.Adapterused for displaying the message-list UI.
SubscribeCallbackis the PubNub listener. The
PubNubinstance accepts instances of the
SubscribeCallbackclass. Changes related to new messages, presence events, and status operations are all dispatched to this listener.
Useris the model for our app users. Every user has a first and last name, UUID, designation, and profile picture. To simplify things, our users are represented as animals and defined statically.
Messageis the model of the messages sent and received on the PubNub network. Its design is simple, as it contains only the
senderIdproperties that are actually transmitted.
Chat UIs can get to be very complex. Messages transmitted from one end to another are often more than just plain text. Images, GIFs, videos, groups of images, URL previews, and other rich media need to be supported.
All possible view types also need to have two variants of their UI—the first one representing a sent message (generally on the right side), and the second, usually on the left side, for received messages. Message grouping by date is also a common scenario, leading to an additional UI type for the day the messages are sent on. Message aggregation is common, too, meaning that the message bubble needs to look different if it's part of a consecutive series of messages from the same sender.
In the application, instances of
Fragment classes have their parent classes:
ParentFragment, respectively. These are just utility classes which handle common lifecycle issues and expose a simplified API to the instances of their child classes. As such, there are out of scope for this tutorial.
MainActivity.java, we create an instance of
PubNub and make it available to
Fragment instances that will originate from the
The easiest way for the
MainActivity.java class to provide the
PubNub instance to its child
Fragments is to create a Java interface:
MainActivity implement it and override the requested methods:
Fragments use the
getPubNub() method to access the
MainActivity.java also takes care of unsubscribing and disconnecting from PubNub:
ChatFragment can send messages to the channel and also hosts our chat list UI with the channel’s history.
All fragments bind themselves to the
ParentActivityImpl interface described above, like so:
This way, fragments are able to access the
Bind activities and fragments
Every fragment hosts its own
PubNub listener, also known as
SubscribeCallback. We attach listeners with
pubNub.addListener(). Listeners deliver any real-time data that comes over the PubNub network, like statuses, messages, and presence events.
To keep the logic of binding listeners with Android
Fragments generic, we create another interface:
Every fragment should implement this interface and expose its own initialized instance of the PubNub listener:
The respective instances of the PubNub listeners must now be attached to the actual
PubNub instance within
ParentFragment class does this automatically:
Listener removal also happens automatically, in the same fashion as attaching:
The communication between key UI components (with
PubNub in mind) should now be established, with a couple of key aspects:
- Fragments can now use the unique PubNub instance from their host Activity
- Attaching and removing PubNub listeners is done in a reliable way
- Unsubscribing and disconnecting from PubNub
Subscribe to a channel
ChatFragment.java is in charge of subscribing to channels. It's host
MainActivity is in charge of creating and attaching it.
susbcribe() method in
ChatFragment.java handles subscription to channels, preferably right after the fragment's
onViewCreated method has completed:
To consume the result of PubNub's subscribe method, we initialize this fragment's PubNub listener within the fragment's
At this point, the client should be subscribed to the channel.
Fetching message history
Once the client is subscribed to a channel, it's able to listen to events related to that channel (such as messages and presence events), and needs to retrieve any previous messages posted to the channel.
fetchHistory() method retrieves previous messages, and should initially be called right after the client subscribes to a channel:
The channel history is fetched in chunks. The initial chunk with the most recent messages is fetched when the client subscribes to the channel, and other chunks (if any) are fetched when the
RecyclerView is about to reach its top. The
RecyclerView.OnScrollListener detects how many items are visible at the top, and decides when to fetch the next chunk:
Methods from the
History.java utility class fetch channel history, and manipulate larger sets of messages to sort, combine, or group them, adapting the messages to the needs of the application's UI.
Here is how fetching channel history looks via the PubNub Java SDK:
Messages from the same sender within a predefined time interval are grouped together:
MessageHelper.java class for an in-depth look at how the message grouping (chaining) works.
Messages are stored within a Java
ChatAdapter are used to bind the list with the UI.
Once the messages are fetched, chained, and organized, the
RecyclerView adapter needs to be updated.
Binding messages with the UI
In this application, there are six different view types for messages; these are equivalent to the
RecyclerView's view types.
When considering message series and the app UI, a message can be displayed in a form of a:
- header message, which is the first message in the message series; the previous message doesn't belong to this series.
- middle message, which is neither the first nor the last message in the series; the previous and next messages belong to the same series as this one.
- end message, which is the last message in the message series; the next one doesn't belong to this series.
There is one mandatory condition for all messages within a series: their author must always be the same user.
Multiply these three types by two (for sent and received messages), for a total of six types. In
onCreateViewHolder method recognizes these view types:
The data source for the
ChatAdapter is a Java list:
Message.java is our own custom class into which we deserialize the actual messages from PubNub:
Updating the chat list
ChatFragment uses a
RecyclerView to display messages. Its data source is a Java list of
Every message arrival is represented with a
Message.java object. Messages can arrive either as a series of messages when fetched from channel history, or as a single message when a new message is sent or received. Either way, it's updated via
DiffUtil from the
Sending a message
A custom view handles sending messages, which helps keep the UI modular and supports sending different types of messages:
The view communicates with
ChatFragment via the
MessageComposer.Listener interface. The fragment that hosts this view (
ChatFragment) implements the interface and sends the message:
To check if the message is sent to the network, we check the result within the
onResponse callback, passed to the publish builder. You can access the new message's timetoken from there, if it's sent, but we obtain the actual data of the sent message via the PubNub listener.
Receiving a message
Messages are received within the
SubscribeCallback instance within the
We serialize the response to a new instance of our
Once the data is serialized, and other data is initialized, we add the
Message to our list of other messages for that channel. Then, we traverse all messages in that particular list to determine if the new message is part of a message series, and determine the appropriate view type.
These tasks happen in a background thread, since they aren't manipulating the UI.
When the data source (list of messages) is ready and updated with the new message, we update the
RecyclerView adapter via
mChatAdapter.update(mMessages) and scroll the chat to the bottom with
scrollChatToBottom() to focus the new message:
Fetching occupancy and online members
ChatInfoFragment.java uses the
hereNow method from the PubNub Java SDK to display currently active users in the channel:
This method returns only the active users for that moment. To receive presence data in real time, you have to listen for that data within the
presence callback. Refer to Listening for changes for more information.
Listening for changes
Changes from the PubNub network are dispatched to the PubNub listener instance, which should be already attached via
presence callback to listen for presence changes:
In this excerpt from
ChatInfoFragment.java, where this data is bound to the UI,
mUsers is the data source for our
RecyclerView adapter on that screen. It's populated according to the presence changes received within the network.
Testing the app
Integration tests are in the
You can run the tests by executing the following command:
The integration tests cover most features of the PubNub Java SDK as seen through the course of this tutorial.
For example, here is a test that verifies whether a message is successfully published:
Set up your own project
Now that you've seen how we built our app, why not get started making your own? If you don't have Android Studio installed, you can download it from the Android Developers site.
To create your Android Studio project, do the following:
From the Welcome to Android Studio window, choose Start a new Android Studio Project.
Select the Empty Activity project type.
Name your project, set the package name and location, choose
Javaas the Language and click
To install the PubNub Java SDK using the Gradle dependency manager, do the following:
Open your module-level
build.gradlefile. It's typically located at
dependenciesblock, add a new entry:
Sync nowto download and install the dependencies.
Using your PubNub keys
Be careful with keys
Never include your keys in an app, or even in your source-code repository. The following sections show some ways to store them elsewhere, and reference them as needed.
You can store your keys in a
gradle.properties file. Since you'll often need to commit a project-level
gradle.properties file to a source code repository, you should create a new
gradle.properties file inside the
app/ module, and add it to your project's
Use your own keys here
Use your own publish and subscribe keys in these commands, replacing
To set up a secure mechanism to store and use your keys, do the following:
In a terminal window in your project's root directory, run the following commands:
Inside your app-level
build.gradle, bind your keys from the
gradle.propertiesfile with actual Java code. Add the two
buildConfigFieldlines within the
Don't change anything else here. The variables are initialized from your
Sync nowto start using the keys.
After Gradle builds the project, you'll be able to use the keys from your Java code: