Build an Android Multi-user, Collaborative To-Do App
In this blog post, we’ll show you how to build a collaborative, multi-user to-do list for Android. The application enables connected users to edit the to-do list remotely, in real time. All the source code for our Android to-do application is available here.
The lessons learned in this tutorial can be applied to pretty much any use case where you need to stream updates bidirectionally between devices! In editing a field in our to-do app, you’re sending a message. This same structure can be applied to chat applications, stock quote streaming, or even Android IoT mobile apps!
The latest release of the app (Tested on Android Kitkat & Lollipop) is available here.
Android To-Do App Overview
This is a collaborative ToDo App which allows users to participate in a project and assign tasks (or ToDo items related to the project) to one another.
The app keeps a track of all tasks added on the system by any user which is then synced up across all other users’ screen in real time. Similarly, updates on the task information are also automatically displayed on all users’ screens. All the data is streamed via PubNub.
Usage and Screenshots
The App allows the user to login as one of the three predefined usernames (Peter, Sam or Eric). Launch the app from two or three different phones by logging in as one of the predefined users. There is no password required for this app.
Once logged in, the user can see the status of task completion and last update time on any task. Initially it will all be blank.
After clicking on the ‘Tasks 0/0’ button, the user can see the current list of tasks and can add a new task by clicking on the new task (+) icon on the top.
Tasks can also be modified by the users and this will be updated on the other users’ app window to allow seamless collaboration. Only the user owning the task can modify it.
The App will also send notification if a new task update is received while the user is busy.
Getting Started with the PubNub Android SDK
So let’s get started with publish/subscribe messaging, the core of sending data bidirectionally between devices.
Most of the code for this app is the standard Android UI code using the native Android SDK which should be familiar to Android developers. Take a look at the source code to get an idea of how we’re structuring our app.
However, let’s dig deeper into integrating pub/sub messaging with Android. We’re using a publish/subscribe design pattern to update and synchronize the data across all devices connected.
We have listed below some of the common challenges and tips to circumvent them, which will help the developers in avoiding some common pitfalls when building a real-time application with Android.
#1 – Initialization of PubNub App Context and callback registration
Problem
In this ToDo App, there are multiple screens which rely of PubNub’s subscribe callback for updating the UI. Initializing PubNub App Context as part of the Activity subclass for each UI screen will lead to multiple object initialization and callback registrations every time the screen is activated and this can lead to unexpected behavior such as multiple invocation of callback function.
Solution
It is better to have a separate background service for managing the PubNub App Context. This service is called during Android boot process with the help of a Broadcast Receiver which listens to “Boot Completed” event. Whenever a PubNub message is received, a broadcast is sent using intent filter. This intent is listened/caught by another Broadcast receiver using the same intent filter which then updates the UI.
Here is how a message received in PubNub channel is handler in the background service.
#2 – Identifying the best channel design pattern for App
Problem
In this ToDo App, every action on a task, right from its creation to completion, needs to be updated on all running instances of the App. So we end up with different types of updates, such as task creation, task info update, task completion. And this leads to different message types being used as a means of communication between the App instances.
Fitting all this into the PubNub channel concept can become a bit confusing for an App developer. This problem is not specific to Android, but can happen in the development phase of any kind of application built using PubNub.
Solution
There are mainly two approaches to handle multifarious message exchange between the app instances.
- Assign a separate PubNub channel to each message type. – In this case, we define separate channels for conveying task addition, task info updates and task completion. Needless to say, the App needs to subscribe to all the three channels in order to receive the updates properly. This will result in different callback functions for handling data from each channel.
- Assign a single common channel for all communication between app instances – In this case, we can define a single channel but the individual message types are distinguished based on a unique tag added at the beginning of every message sent on channel. This will result in one callback function handling data from all channels.
Different Approaches for Different Use Cases
Now the question is which approach is better? And there is no perfect answer for that.
The decision to use one of the above option depends on the trade-offs we are willing to compromise. In terms of source code complexity and maintainability, the first option is always better because it allows better code organization with well-defined separation of responsibility which will be ideal for future source code maintenance of app. The second option is the simpler one and an obvious choice for smaller applications.
Considering that this to-do is a demo application, we have gone ahead with the second option, but for any production application which is expected to have long feature road-map and development cycle, it is always recommended to follow the first approach for better code maintainability.
Below is the code gist for the publish call on the App. You can see that all messages are published in the same channel and are prefixed with a string tag which can be either “NEW” or “MODIFY” to identify the message type.
#3 – Android UI update from PubNub’s subscribe callback.
Problem
All applications using the PubNub Android SDK will rely on the publish/subscribe pattern to sync data updates across all running instances of the app. This may lead to UI updates on the app screen. As it turns out, attempting to update the UI directly from the subscribe callback will lead to an error which might read something like this.
“android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.”
Solution
Android modifies the user interface and handles input events from one single user interface thread, which is the main thread. To provide a good user experience, all potentially slow running operations in an Android application should run asynchronously. This can be done either by creating Java threads or Android’s concurrency constructs such as Handlers and Asynctask.
PubNub has made the whole process much easier by creating its own subscribe mechanism. It is like an event handling mechanism in which once we subscribe to a channel, whenever a message arrives in channel , the successCallback() function is called.
Point to be noted is that, the callback function does not still have access to the objects created in the main UI thread. Hence to do UI updates from callback function, we need to get the current Activity’s instance and its runOnUIThread() function needs to be called.
This problem can also be entirely avoided by using a background service as mentioned in #1 but for those application where a persistent background connection is not required, the above approach can work well.
Wrapping Up
This app is first in the series of tutorials we are building to showcase PubNub with Android. Stay tuned for future versions of this app where we will cover some of the other important features like presence, storage and playback, access management etc. and demonstrate how these PubNub features can be incorporated in Android to build a full featured application.