I'm building a chat application, so I'm using two ListViews: one that shows the online friends and one for the chat itself, that receives the messages and so on. I'm using the XMPP protocol and the Smack Library for Android.
The Smack Library give me Listeners which are activated every time a friend status changes(online/offline) and the other one when the user receives a message. Here's how I declare the adapter and call an AsyncTask when the user press a button:
peopleList = (ListView) findViewById(R.id.peopleList);
adapter = new MyAdapter(this, people);
peopleList.setAdapter(adapter);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new ConnectAndLoad(MainActivity.this).execute();
}
});
Inside the AsyncTask, I connect to the server inside the doInBackground method and inside the onPostExecute I create the listener which adds the user to the array list of the listview and call adapter.notifyDataSetChanged();
public class ConnectAndLoad extends AsyncTask<String, Integer, Boolean> {
private ProgressDialog dialog;
public ConnectAndLoad(Activity activity)
{
this.dialog = new ProgressDialog(activity);
this.dialog.setTitle("Loading..");
this.dialog.setMessage("Connecting to the server..");
dialog.show();
}
@Override
protected Boolean doInBackground(String... arg0) {
MyConnectionManager.getInstance().setConnectionConfiguration(getApplicationContext());
MyConnectionManager.getInstance().connect();
MyConnectionManager.getInstance().login();
return true;
}
protected void onPostExecute(Boolean boo)
{
MyConnectionManager.getInstance().bored();
Roster roster = Roster.getInstanceFor(MyConnectionManager.getInstance().getConnection());
try
{
if (!roster.isLoaded()) roster.reloadAndWait();
}
catch (Exception e)
{
Log.e(TAG, "reload");
}
roster.addRosterListener(new RosterListener() {
public void entriesDeleted(Collection<String> addresses) {
}
public void entriesUpdated(Collection<String> addresses) {
}
public void entriesAdded(Collection<String> addresses) {
}
@Override
public void presenceChanged(Presence presence) {
people.add(new People(presence.getFrom(), presence.getStatus()));
adapter.notifyDataSetChanged();
}
});
dialog.dismiss();
}
}
And below is my Custom Adapter:
public class PeopleAdapter extends ArrayAdapter<People> {
private ArrayList<People> events_list = new ArrayList<>();
Context context;
public PeopleAdapter(Context context, ArrayList<People> users) {
super(context, 0, users);
this.context = context;
this.events_list = users;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
People user = getItem(position);
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.people_list, parent, false);
}
TextView tvName = (TextView) convertView.findViewById(R.id.name);
TextView tvStatus = (TextView) convertView.findViewById(R.id.status);
tvName.setText(user.name);
tvStatus.setText(user.status);
convertView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(context, "You Clicked " + events_list.get(position).name, Toast.LENGTH_SHORT).show();
Intent i = new Intent(context, ConversationActivity.class);
i.putExtra("user", events_list.get(position).name);
context.startActivity(i);
}
});
return convertView;
}
}
I mean what I want to do I think it's a simple thing, every single chat app does it, is basically update the list view automatically but I'm having two problems:
I receive this error every time the list view updates (the app keeps working though):
Exception in packet listener: android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
I can give you a simple solution. Make local Activity variable in the ConnectAndLoad class
private Activity activity;
public ConnectAndLoad(Activity activity)
{
...
activity.activity= activity;
}
Instead on directly calling adapter.notifyDataSetChanged();
use
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
adapter.notifyDataSetChanged();
}
});
It seems like presenceChanged()
called in another thread. But be careful and make sure you delete RosterListener when activity gets destroyed or it can lead to the memory leaks i.e activity is already destroyed but you keep getting notifications about presence change.