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>
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!