Search code examples
javaandroidlistviewandroid-listviewpubnub

Android - Custom Listview for a pubnub based chat


I'm building an Android app that uses pubnub to allow users to chat to eachother. Usernames and chat channels are set up via Parse and I've managed to get the publish and subscribe part of the chat working. The next part that I'm trying to do is set up a custom listview for the chat to be displayed in.

I'd like the chat to display in bubbles like whatsapp etc. with messages going either side of the screen.
I've created a custom chat_item.xml to hold each row in the listview, a ChatArrayAdapter.java and a ChatMessage.java object to hold each message.
I know that I'm very close to having it working but the structure of it is wrong somewhere along the line and every time a message is received and added to the list I am getting an Only the original thread that created a view hierarchy can touch its views. exception on line 28 of my ChatArrayAdapter.java which is super.add(object); within:

@Override
    public void add(ChatMessage object) {

        chatMessageList.add(object);
        super.add(object);
    }. 

Can anyone advise on what I am doing wrong here? I feel like I'm close to having it working and the building blocks are there, it's just not been built in the correct way.

ChatActivity.java:

    public class ChatActivity extends Activity {

        final Pubnub pubnub = new Pubnub("example", "example");

        TextView tvBtnBackToSelection;
        TextView tvBtnNewChat;
        EditText etMessageText;
        Button btnSendMessage;

        private ChatArrayAdapter chatArrayAdapter;
        private ListView listView;

        String USER_ID;
        String CHAT_ID;
        String CHAT_STATUS;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_chat);

            //get data passed from last activity by intent
            Intent intent = getIntent();
            USER_ID = intent.getStringExtra("USER_ID");
            CHAT_ID = intent.getStringExtra("CHAT_ID");
            CHAT_STATUS = intent.getStringExtra("CHAT_STATUS");

            //initialise ui and pubnub
            uiInit();

            //subscribe to chat channel
            subscribeToChat(CHAT_ID);

            //initialise chat presence
            chatPresenceInit(CHAT_ID);

        }

        //-----inistialise ui
        public void uiInit() {

            listView = (ListView) findViewById(R.id.lvChatList);

            chatArrayAdapter = new ChatArrayAdapter(getApplicationContext(), R.layout.chat_item, USER_ID);
            listView.setAdapter(chatArrayAdapter);

            tvBtnBackToSelection = (TextView) findViewById(R.id.tvBtnBackToSelection);
            tvBtnNewChat = (TextView) findViewById(R.id.tvBtnNewChat);
            etMessageText = (EditText) findViewById(R.id.etMessageText);
            btnSendMessage = (Button) findViewById(R.id.btnSendMessage);

            tvBtnBackToSelection.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {

                    backToSelection();
                }
            });

            tvBtnNewChat.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {

                }
            });

            btnSendMessage.setOnClickListener(new View.OnClickListener() {

                @Override
                public void onClick(View v) {

                    //publish message to chat
                    publishToChat(etMessageText.getText().toString());

                    //set edittext back to null
                    etMessageText.setText("");
                }
            });

        }

//-----subscribe to chat channel
        public void subscribeToChat(String chatChannel) {

            try {

                pubnub.subscribe(chatChannel, new Callback() {

                            @Override
                            public void connectCallback(String channel, Object message) {
                                System.out.println("SUBSCRIBE : CONNECT on channel:" + channel
                                        + " : " + message.getClass() + " : "
                                        + message.toString());

                            }

                            @Override
                            public void disconnectCallback(String channel, Object message) {
                                System.out.println("SUBSCRIBE : DISCONNECT on channel:" + channel
                                        + " : " + message.getClass() + " : "
                                        + message.toString());
                            }

                            public void reconnectCallback(String channel, Object message) {
                                System.out.println("SUBSCRIBE : RECONNECT on channel:" + channel
                                        + " : " + message.getClass() + " : "
                                        + message.toString());
                            }

                            @Override
                            public void successCallback(String channel, Object message) {

                                String messageSplit[] = message.toString().split("_");;
                                String receivedUserID = messageSplit[0];
                                String receivedMessage = messageSplit[1];

                                System.out.println("CHANNEL: " + channel);
                                System.out.println("USERID: " + receivedUserID);
                                System.out.println("MESSAGE: " + receivedMessage);

                                chatArrayAdapter.add(new ChatMessage(receivedUserID, receivedMessage));
                            }

                            @Override
                            public void errorCallback(String channel, PubnubError error) {

                                System.out.println("SUBSCRIBE : ERROR on channel " + channel
                                        + " : " + error.toString());
                            }
                        }
                );

            }
            catch(Exception e) {

            }


        }

        //-----publish to chat channel
        public void publishToChat(String message) {


            message = message.replace("_", "").trim();
            message = USER_ID + "_" + message;

            //set up publish callback
            Callback callback = new Callback() {
                public void successCallback(String channel, Object response) {
                    System.out.println(response.toString());
                }
                public void errorCallback(String channel, PubnubError error) {
                    System.out.println(error.toString());
                }
            };

            //publish message to channel
            pubnub.publish(CHAT_ID, message, callback);
        }
    }

ChatMessage.java:

public class ChatMessage {

    public String user;
    public String message;

    public ChatMessage(String user, String message) {
        super();

        this.user = user;
        this.message = message;
    }
}

ChatArrayAdapter.java

public class ChatArrayAdapter extends ArrayAdapter<ChatMessage> {

    public TextView tvChatLeft;
    public TextView tvChatRight;
    private List<ChatMessage> chatMessageList = new ArrayList<ChatMessage>();
    String thisUserId;

    @Override
    public void add(ChatMessage object) {

        chatMessageList.add(object);
        super.add(object);
    }

    public ChatArrayAdapter(Context context, int textViewResourceId, String userId) {
        super(context, textViewResourceId);


        thisUserId = userId;
    }

    public int getCount() {

        return this.chatMessageList.size();
    }

    public ChatMessage getItem(int index) {

        return this.chatMessageList.get(index);
    }

    public View getView(int position, View convertView, ViewGroup parent) {

        View row = convertView;

        if (row == null) {

            LayoutInflater inflater = (LayoutInflater) this.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            row = inflater.inflate(R.layout.chat_item, parent, false);
        }

        ChatMessage chatMessageObj = getItem(position);

        tvChatRight = (TextView)row.findViewById(R.id.tvChatBubbleRight);
        tvChatLeft = (TextView)row.findViewById(R.id.tvChatBubbleLeft);

        //create flag to define if message sender is current user
        final boolean isMe = chatMessageObj.user.equals(thisUserId);

        if (isMe) {

            //get message text from parse
            String messageText = chatMessageObj.message;

            //if message text is not null
            if (!messageText.equals(null)||!messageText.equals("")||!messageText.equals(" ")) {

                //set message to right chat bubble
                tvChatRight.setText(messageText);

                //set right textview bubble gravity to right and set as visible
                tvChatRight.setGravity(Gravity.RIGHT);
                tvChatRight.setVisibility(View.VISIBLE);

                //hide left textview bubble
                tvChatLeft.setVisibility(View.GONE);
            }

            //if a valid message has not been found
            else {


            }
        }

        //other user
        else {

            //get message text from parse
            String messageText = chatMessageObj.message;

            //if message text is null, only update the users location
            if (!messageText.equals(null)||!messageText.equals("")||!messageText.equals(" ")) {

                //set left chat bubble at message text
                tvChatLeft.setText(messageText);

                //set left chat bubble to left and visible
                tvChatLeft.setGravity(Gravity.LEFT);
                tvChatLeft.setVisibility(View.VISIBLE);

                //hide right chat bubble
                tvChatRight.setVisibility(View.GONE);
            }

            //else if a valid message is found then update chat list as well as users location
            else {


            }
        }

        return row;
    }

    public Bitmap decodeToBitmap(byte[] decodedByte) {
        return BitmapFactory.decodeByteArray(decodedByte, 0, decodedByte.length);
    }

chat_item.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical" >

    <TextView
        android:textSize="12sp"
        android:id="@+id/tvChatBubbleLeft"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/chat_bubble_left"
        android:layout_gravity="left"
        android:layout_weight="1"
        android:gravity="left">
    </TextView>

    <TextView
        android:textSize="12sp"
        android:id="@+id/tvChatBubbleRight"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/chat_bubble_right"
        android:layout_gravity="right"
        android:gravity="right"
        android:layout_weight="1">
    </TextView>

</LinearLayout>

Solution

  • Managed to get this working. I simply added

    ChatActivity.this.runOnUiThread(new Runnable() {
    
                                    @Override
                                    public void run() {
    
                                        chatArrayAdapter.add(new ChatMessage(receivedUserID, receivedMessage));
                                    }
                                });
    

    within the success callback of my pubnub subscribe method and now everything works!