Collin
Collin

Reputation: 45

Android issue updating ListView dynamically based off sockets

I have an android application that is a client for a simple chat server. I am able to connect to the server and my ObjectStreams. The problem is when I receive a message, the thread that handles my server connection calls upon my display message which updates the list view.

I am getting the error "only the original thread that created a view hierarchy can touch its views."

I am pretty sure its because I'm calling my displayMessage() method from my connect thread, but I am not sure how to organize my threads to have a connection to the server and dynamically update my listview.

Here is my main activity.

public class MainActivity extends Activity {

private Connection serverConnection;
private ArrayList<String> listItems = new ArrayList<String>();
private ArrayAdapter<String> adapter;
/**
 * Sets the ArrayAdaptor, and starts the connectThread. 
 */
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    runOnUiThread(new Runnable() {
        public void run() {
            ListView listview = (ListView) findViewById(R.id.list);
            adapter = new ArrayAdapter<String>(MainActivity.this,
                    android.R.layout.simple_list_item_1, 
                            listItems);
            listview.setAdapter(adapter);
        }
    }); 
    /**
     * Starts a new connection Thread
     */
    Thread connectThread = new Thread(new Runnable(){
        public void run(){
            serverConnection = new Connection(MainActivity.this);
            serverConnection.run();
        }
    });
    connectThread.start();
}
/**
 * Adds a message to the list view. 
 * @param string - message to be added. 
 */
public void displayMessage(String string) {
    listItems.add(string);
    adapter.notifyDataSetChanged();
}
}

Here is my connection thread class.

public class Connection extends Thread {

private Socket client;
private ObjectOutputStream output;
private ObjectInputStream input;
private MainActivity mainActivity;
private String message;
/**
 * Constructor starts the socket and ObjectStreams
 * 
 * @param mainActivity - reference to the MainActivity
 */
public Connection(MainActivity mainActivity) {
    this.mainActivity = mainActivity;
    try {
        client = new Socket("192.168.1.105", 50499);
        mainActivity.displayMessage("Connected to: "
                + client.getInetAddress().getHostName());
        output = new ObjectOutputStream(client.getOutputStream());
        output.flush();
        input = new ObjectInputStream(client.getInputStream());
    } catch (IOException e) {
        e.printStackTrace();
    }

}
/**
 * Run method for the Thread. 
 */
public void run() {
    for (;;) {
        try {
            message = (String) input.readObject();
            mainActivity.displayMessage(message);
        } catch (OptionalDataException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
}

Upvotes: 1

Views: 1833

Answers (1)

Raghunandan
Raghunandan

Reputation: 133560

You are updating Ui on the background thread. You should update ui on the ui thread. Move your code that updates ui in the background thread. You are refreshing your listview on the background thread.

 mainActivity.displayMessage("Connected to: "
            + client.getInetAddress().getHostName()); 
 mainActivity.displayMessage(message);

 public void displayMessage(String string) {
   listItems.add(string);
   adapter.notifyDataSetChanged(); 
   }

The above should be outside the thread or You can use runonuithread inside the thread to update ui.

      runOnUiThread(new Runnable() {
            @Override
            public void run() {
               // update ui 
            }
        });

Another way would be to use asynctask. Do all your network related operation in doInbackground() and update ui in onPostExecute().

Async Task

Edit: Not sure what you are trying to do.

 public class MainActivity extends Activity {

 private Connection serverConnection;
 private ArrayList<String> listItems = new ArrayList<String>();
 private ArrayAdapter<String> adapter;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
        ListView listview = (ListView) findViewById(R.id.lv);
        adapter = new ArrayAdapter<String>(MainActivity.this,
                android.R.layout.simple_list_item_1, 
                        listItems);
        listview.setAdapter(adapter);
     // use a button and on button click start the thread.

Thread connectThread = new Thread(new Runnable(){
    public void run(){
        serverConnection = new Connection(MainActivity.this);
        serverConnection.run();
    }
});
connectThread.start();
}

public void displayMessage(String string) {
listItems.add(string);
adapter.notifyDataSetChanged();
}
class Connection extends Thread {

private Socket client;
private ObjectOutputStream output;
private ObjectInputStream input;
private MainActivity mainActivity;
private String message;

public Connection(MainActivity mainActivity) {
this.mainActivity = mainActivity;
try {
    client = new Socket("192.168.1.105", 50499);
    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            displayMessage("Connected to: "
                   );
        }
    });

    output = new ObjectOutputStream(client.getOutputStream());
    output.flush();
    input = new ObjectInputStream(client.getInputStream());
} catch (IOException e) {
    e.printStackTrace();
}
}

 public void run() {
 for (;;) {
    try {
          message = (String) input.readObject();
          runOnUiThread(new Runnable() {
              @Override
              public void run() {
                displayMessage(message);
              }
          });



    } catch (OptionalDataException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}
}
}
}

Upvotes: 2

Related Questions