1

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();
        }
    }
}
}
Collin
  • 45
  • 2
  • 8

1 Answers1

2

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();
    }
}
}
}
}
Raghunandan
  • 132,755
  • 26
  • 225
  • 256
  • I'm a little confused by where and what your telling me to move. My displayMessage() in my MainActivity is outside the runOnUiThread. I will look into the Async Task, just want to learn about how my threads are working here first. – Collin May 29 '13 at 16:34
  • 1
    @Collin In your displayMessage method you are refreshing listview. and your display method is called from the thread. So update ui on the ui thread using runOnUiThread or use asyntask. check the link provided in the link – Raghunandan May 29 '13 at 16:36
  • How can I pass the message from my Connection thread to the runOnUiThread? I can't create a runOnUiThread in my Connection class. – Collin May 29 '13 at 17:00
  • That will not work because the Connection class is a extends a thread and doesn't extend an activity which contains the method runOnUiThread. – Collin May 29 '13 at 17:21
  • 1
    @Collin that's why in the edit i made the connection class inner class of your activity class – Raghunandan May 29 '13 at 17:23
  • Ahh thank you now that makes sense I didn't realize you made it an innerclass – Collin May 29 '13 at 17:25
  • 1
    Check and run the same. if it helps mark the answer as accepted by clicking the tick once. You can also upvote if you have enough reputation. If you have any queries let me know – Raghunandan May 29 '13 at 17:34
  • I gave you an upvote and accepted, but it now seems that I am getting the error "android.os.strictmode$androidblockguardpolicy.onnetwork" from my research it seems that it is because I am running network operations on the main thread. – Collin May 29 '13 at 19:11
  • http://stackoverflow.com/questions/15921252/exception-on-android-4-0-android-os-strictmodeandroidblockguardpolicy-onnetwor. Check if you are running any network related operation on the main ui thread. If so move it to a background thread – Raghunandan May 29 '13 at 19:12
  • 1
    I guess you should move the code in your constructor of the Connection class to run method. Also in such situations i suggest you use asynctask. It is easy. you have 3 overriden methods. onPreExecute() invoked on ui thread. onPostExecute() involed on ui thread and doInbackground() for network related operation. So its easier and no confusion – Raghunandan May 29 '13 at 19:18
  • 1
    My plan was to change my code to use async task. I just wanted to work with threading for learning and practice – Collin May 29 '13 at 19:41