Live Conversation Updates for New Messages in an Android Chat Application

Throughout the Android chat app development series, we’ve built an app that allows users to register, send/receive text messages, open conversations, and receive notifications for incoming messages.

One additional essential feature for chat apps is the ability to update the active, live conversation. Once a new message arrives for the currently active conversation, it should be updated to reflect the new changes (either sent/received messages).

This is compared to the legacy way of manually refreshing the conversation to see the latest updates.

In this tutorial, we’re going to update the currently active conversation for the newly sent/received messages. Once a conversation is opened in the Android app, a background thread runs to continuously check (the Flask server) for new messages within the current conversation.

Once the server has received new messages, it will respond with a JSON object containing such messages. The Android app then updates the ListView of the active conversation to add new items for the new messages.

The GitHub project of this project is available on here. The Android app is available on the Google Play Store under the name HiAi Chat. Feel free to download and try it out:

Updating the Server to Fetch New Messages

The first thing we need to do is to edit the Flask server to create a function that responds to the new messages sent within a conversation. Remember that all requests sent to the server are handled by a function named chat(). Based on the subject field sent within the request, the chat() function selects the appropriate function to handle the request.

For the purposes of this tutorial, a new condition is added to call a function named receive_new_messages() to handle the requests where the subject is set to receive_new_messages. The job of this function is to check to see if there are any new messages sent within the currently-opened conversation in the Android app and reply with a JSON object with such new messages:

@app.route('/', methods = ['GET', 'POST'])
def chat():
    msg_received = flask.request.get_json()
    msg_subject = msg_received["subject"]

    if msg_subject == "register":
        return register(msg_received)
    elif msg_subject == "login":
        return login(msg_received)
    elif msg_subject == "verify":
        return verify(msg_received)
    elif msg_subject == "send":
        return send(msg_received)
    elif msg_subject == "receive_chats":
        return receive_chats(msg_received)
    elif msg_subject == "receive_messages":
        return receive_messages(msg_received)
    elif msg_subject == "check_new_messages":
        return check_new_messages(msg_received)
    elif msg_subject == "receive_new_messages":
        return receive_new_messages(msg_received)
    else:
        return "Invalid request."

The implementation of the receive_new_messages() function is shown below. Let’s discuss the parts of this function:

def receive_new_messages(msg_received):
    receiver_username = msg_received["receiver_username"]
    sender_username = msg_received["sender_username"]

    select_query = "SELECT last_date_conversations_fetched FROM users WHERE username = " + "'" + receiver_username + "'"
    db_cursor.execute(select_query)
    records = db_cursor.fetchall()
    if len(records) == 0:
        return "Invalid receiver username."

    last_date_messages_fetched = str(records[0][0])

    select_query = "SELECT message, ADDTIME(receive_date, '06:00:00'), sender_username FROM messages where ((receiver_username = " + "'" + receiver_username + "' AND sender_username = " + "'" + sender_username + "') OR (receiver_username = " + "'" + sender_username + "' AND sender_username = " + "'" + receiver_username + "')) AND  receive_date > " + "'" + last_date_messages_fetched + "' ORDER BY receive_date DESC"
    db_cursor.execute(select_query)
    records = db_cursor.fetchall()
    if len(records) == 0:
        print("No new messages delivered for username " + receiver_username)
        return "0"

    update_query = "UPDATE users SET last_date_conversations_fetched = CURRENT_TIMESTAMP WHERE username = " + "'" + receiver_username + "'"
    db_cursor.execute(update_query)
    chat_db.commit()

    new_messages = {}
    for record_idx in range(len(records)):
        curr_record = records[record_idx]
        message = curr_record[0]
        receiveDate = curr_record[1]
        sender_username = curr_record[2]

        curr_message = {}
        curr_message["message"] = message
        curr_message["date"] = str(receiveDate)
        curr_message["sender_username"] = sender_username

        new_messages[str(record_idx)] = curr_message

    new_messages = json.dumps(new_messages)
    print("Sending message(s) :", new_messages)
    return new_messages

The first 2 lines in the function return the usernames of the 2 users participating in the current conversation:

The next few lines select the last_date_conversations_fetched column from the users table. This column reflects the last date that the logged-in user in the Android app fetched the messages.

The last_date_conversations_fetched column helps us understand whether or not there are any new messages sent/received between the sender and receiver within the conversation.

According to the next SELECT statement, the messages with the receive_date column set to a date higher than the one available in the last_date_conversations_fetched column are regarded as new messages that the user didn’t fetch. Thus, they’re returned from the messages table. If there are no new messages, then 0 is returned, reflecting that no new messages exist.

After the new messages are fetched, the last_date_conversations_fetched column is set to the current time to avoid receiving the previous messages again.

Finally, the newly-fetched messages are prepared in a JSON object according to the following code. This object is then returned to the Android app:

This above entails the necessary edits in the Flask server itself. The next section works on the Android app.

Editing the Android App to Check and Retrieve New Messages

The class of the Android project that’s responsible for retrieving the messages within a conversation is named MessageListViewActivity.

Currently, all it can do is fetch the messages sent/received within a conversation at the time it’s opened, but it ins’t updated by the new messages sent/received after that time.

To tackle this, a method named setAlarmForNewMessages() starts a thread that continuously checks for new messages. An instance of the Handler class is defined in the class header, which is named updateMessagesHandler:

The thread is implemented in the Task class, as shown in the code below. The thread calls a method named getMessages() every second that’s responsible for sending a request to the server, checking if new messages exist.

To access the thread outside the Task class, the thread is assigned to the updateMessagesRunnable variable defined in the class header:

private Runnable updateMessagesRunnable;

class Task implements Runnable {
    @Override
    public void run() {
        updateMessagesHandler.post(new Runnable() {
            @Override
            public void run() {
                MessageListViewActivity messageListViewActivity = new MessageListViewActivity();
                messageListViewActivity.getMessages(replyReceiverUsername, messageListView, receivedMessages, sendersUsernames, messagesDates);
                updateMessagesHandler.postDelayed(this, 1000);
                updateMessagesRunnable = this;
            }
        });
    }
}

The implementation of the getMessages() method is given below. The created JSON object has the field subject set to receive_new_messages to inform the server that just new messages are to be returned:

    public void getMessages(String senderUsername, ListView messageListView, ArrayList<String> receivedMessages, ArrayList<String> sendersUsernames, ArrayList<String> messagesDates) {
        JSONObject messageContent = new JSONObject();
        try {
            messageContent.put("subject", "receive_new_messages");
            messageContent.put("receiver_username", MainActivity.loginUsername);
            messageContent.put("sender_username", senderUsername);
        } catch (JSONException e) {
            e.printStackTrace();
        }

        RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), messageContent.toString());

        Log.d("UPDATED_MSGS", "Creating an HTTP post request to check for new messages between the user and " + senderUsername);
        postRequest3(MainActivity.postUrl, body, senderUsername, messageListView, receivedMessages, sendersUsernames, messagesDates);
    }

For sending the request to the server and handling its response, the postRequest3() method is used, as implemented below. If the JSON object isn’t empty, then new items are added within the ListView of the conversation.

public void postRequest3(String postUrl, RequestBody postBody, final String senderUsername, final ListView messageListView, final ArrayList<String> receivedMessages, final ArrayList<String> sendersUsernames, final ArrayList<String> messagesDates) {
        OkHttpClient client = new OkHttpClient();

        Request request = new Request.Builder()
                .url(postUrl)
                .post(postBody)
                .header("Accept", "application/json")
                .header("Content-Type", "application/json")
                .build();

        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                // Cancel the post on failure.
                call.cancel();
                Log.d("UPDATED_MSGS", "The post failed to be delivered : " + e.getMessage());
                e.printStackTrace();

                // In order to access the TextView inside the UI thread, the code is executed inside runOnUiThread()
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Log.d("UPDATED_MSGS", "Failed to Connect to Server to fetch updated messages. Please Try Again.");
                        Toast.makeText(messageListViewActivityContext, "Failed to Connect to Server. Please Try Again.", Toast.LENGTH_LONG).show(); // A message indicating that no messages are delivered for the user.
                    }
                });
            }

            @Override
            public void onResponse(Call call, final Response response) {
                // In order to access the TextView inside the UI thread, the code is executed inside runOnUiThread()
                try {
                    String responseString = response.body().string().trim();

                    if (responseString.equals("0")) {
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                Log.d("UPDATED_MSGS", "There are no new messages between the user and " + senderUsername + ".");
                            }
                        });
                        return;
                    }

                    Log.d("UPDATED_MSGS", "Response from the server as it is : " + responseString);
                    JSONObject messageContent = new JSONObject(responseString);
                    Log.d("UPDATED_MSGS", "Server responded by the messages in the conversation between the user and " + senderUsername + ", " + messageContent);
                    try {
                        for (int i = 0; i < messageContent.length(); i++) {
                            JSONObject currMessage = messageContent.getJSONObject(i + "");
                            String textMessage = currMessage.getString("message");
                            String messageDate = currMessage.getString("date");
                            String messageSenderUsername = currMessage.getString("sender_username");

                            receivedMessages.add(0, textMessage);
                            sendersUsernames.add(0, messageSenderUsername);
                            messagesDates.add(0, messageDate);
                        }
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }

                    int numUpdatedMessages = receivedMessages.size();
                    if(numMessages == numUpdatedMessages){
                        Log.d("UPDATED_MSGS", "No new messages in the conversation between the user and " + senderUsername);
                        return;
                    }

                    Log.d("UPDATED_MSGS", "There are nNew messages in the conversation between the user and " + senderUsername + " and they are fetched successfully.");

                    Log.d("UPDATED_MSGS", "Updated messages in the conversation between the user and " + senderUsername + " are as follows : " + receivedMessages.toString());

                    final ArrayAdapter adapter = new ArrayAdapter(MainActivity.mainActivityContext, R.layout.message_list_item, R.id.messageView, receivedMessages) {
                        @Override
                        public View getView(int position, View convertView, ViewGroup parent) {
                            View view = super.getView(position, convertView, parent);
//                TextView text1 = view.findViewById(android.R.id.text1);
//                TextView text2 = view.findViewById(android.R.id.text2);
                            TextView messageView = view.findViewById(R.id.messageView);
                            TextView senderView = view.findViewById(R.id.senderView);
                            TextView dateView = view.findViewById(R.id.dateView);

                            messageView.setText(receivedMessages.get(position));
                            senderView.setText(sendersUsernames.get(position));
                            dateView.setText(messagesDates.get(position));

                            if (senderUsername.equals(sendersUsernames.get(position))) {
                                view.setBackgroundColor(Color.parseColor("#FFFFFF"));
                            } else {
                                view.setBackgroundColor(Color.parseColor("#d8e3eb"));
                            }
                            return view;
                        }
                    };
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Log.d("UPDATED_MSGS", "Updating the ListView Adapter by the new messages in the conversation between the user and " + senderUsername + " are as follows : " + receivedMessages.toString());
                            messageListView.setAdapter(adapter);
                        }
                    });

                } catch (Exception e) {
                    Log.d("UPDATED_MSGS", "Unexpected error happended while fetching the updated messages " + messageListView + " " + e.getMessage());
                    e.printStackTrace();
                }
            }
        });
    }

Now the Android app is able to receive live updates for the currently-opened conversation.

Conclusion

This tutorial ahas given our Android chat application the ability to instantly receive recently sent/received messages within currently-active conversations in real-time.

The Flask server filters the messages so that the messages with their receive_date column set to a higher value than the last_date_conversations_fetched column are regarded as new messages and returned back to the server.

The Android client continuously checks for new messages and updates the ListView of the conversation once new messages are received from the server.

This tutorial marks the end of our series of building a chat system for Android. Both the Android client and the Flask server were implemented from scratch. The system allows new users to register, verify via email, login/logout after registration & verification, send/receive text messages, fetch conversations, and receive instant notifications for new messages.

The series consists of 7 tutorials (including this one) and the titles of the previous 6 one are as follows:

  1. Building an Android Login System
  2. Email Verification for an Android App Registration System
  3. Sending text messages on Android between verified users
  4. Receiving Conversation Text Messages for an Android Chat Application
  5. Instant Notifications for New Messages in an Android Chat Application
Avatar photo

Fritz

Our team has been at the forefront of Artificial Intelligence and Machine Learning research for more than 15 years and we're using our collective intelligence to help others learn, understand and grow using these new technologies in ethical and sustainable ways.

Comments 0 Responses

Leave a Reply

Your email address will not be published. Required fields are marked *