error: Only the original thread that created a view hierarchy can touch its views
Asked Answered
E

5

13

Hi and thank you for looking at my question. I am an intermediate programmer in C but an Android newbie. I have been trying to get a chat programming working. Assuming everything else in the code below works perfectly. The one question I like to ask is when I try to setText() from a thread running, I get an exception above. I looked at many many websites and here too. Found many things, but I really do not understand. Please explain to me in the most simple way or offer me some simple fix if possible.

Thank you very much!!

public class chatter extends Activity {

private String name = "Unknown User";

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);



    final EditText msgToServer = (EditText) findViewById(R.id.msgBox);
    final EditText chatFromServer = (EditText) findViewById(R.id.chatBox); 

    final Button MsgToServer = (Button) findViewById(R.id.sendButton);

    Socket socket = null;
    String ipAddress = "192.168.1.103";
    try {
        InetAddress serverAddr = InetAddress.getByName(ipAddress);
        Socket socketMain = new Socket(serverAddr, 4444);
        socket = socketMain;
    } catch (IOException e) {
        // TODO Auto-generated catch block
        Log.e("TCP", "error", e);
    }

    final OutMsg outMsg = new OutMsg(socket);
    Thread msgSenderThread = new Thread(outMsg);
    msgSenderThread.start();

    //chatFromServer.post(new InMsg(socket, chatFromServer));
    Thread msgReceiverThread = new Thread(new InMsg(socket, chatFromServer));
    msgReceiverThread.start();

    MsgToServer.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            String msgToServerString; 
            msgToServerString = msgToServer.getText().toString();
            outMsg.message = name + ": " + msgToServerString;
            outMsg.readyToSend = true;
            msgToServer.setText("");
        }
    });
}

public void updateResultsInUi (String msg)
{
    final EditText chatFromServer = (EditText) findViewById(R.id.chatBox); 
    chatFromServer.setText(msg); 
}

public class InMsg implements Runnable {

    Socket socket;
    EditText chatFromServer;
    public InMsg(Socket socket, EditText chatFromServer)
    {
        this.socket = socket;
        this.chatFromServer = chatFromServer;
    }

    public void run(){
        try {
        BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String str = "FIRSTMESSAGEFROMSERVER";
            while (true)
            {
                if (str.equals("FIRSTMESSAGEFROMSERVER"))
                    str = in.readLine();
                else
                    str = str + "\n" + in.readLine();
                Log.e("TCP", "got the message: " + str);
     //Here is where went wrong******************
                chatFromServer.setText(str);
     //******************************************
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            Log.e("TCP", "error in receiving", e);
        }
    }

}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.menu, menu);
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Handle item selection
    switch (item.getItemId()) {
    case R.id.setNameMenu:
        setname();
        return true;
    default:
        return super.onOptionsItemSelected(item);
    }
}

public void populateChatBox (String msgFromS)
{
    Log.e("TCP", "going in to popC");
    final EditText textNameInput = (EditText) findViewById(R.id.nameBox);
    Log.e("TCP", " popC");
    textNameInput.setText(msgFromS);
    Log.e("TCP", "going out from popC");
}

public void setname()
{
    setContentView(R.layout.custom_dialog);
    final EditText textNameInput = (EditText) findViewById(R.id.nameBox);
    Button submitNameButton = (Button) findViewById(R.id.submitNameButton);
    submitNameButton.setOnClickListener(new OnClickListener() {
    @Override
        public void onClick(View v) {
        String nameinput = textNameInput.getText().toString();
            if (!name.equals(""))
                name = nameinput;
            setContentView(R.layout.main);
        }
    });
}
}
Emblazonment answered 23/2, 2011 at 21:18 Comment(0)
B
31

In your run() method:

Message msg = new Message();
String textTochange = "text";
msg.obj = textTochange;
mHandler.sendMessage(msg);

Create the mHandler in your UI thread;

Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            String text = (String)msg.obj;
            //call setText here
        }
};
Ballistic answered 23/2, 2011 at 21:29 Comment(1)
handleMessage doesn't appear to be a super class method for Handler; why do you have @Override? I get an error when I try this.Polymorphous
B
3

You are not on the UI thread when setting the text. You need to be on UI if you want to work on UI items. Create a message handler on the UI thread, post your messages to it and call setText from the handler on the UI thread.

Ballistic answered 23/2, 2011 at 21:24 Comment(1)
yes, i have seen similar answers in other questions, it is just too general. Not that you are wrong, but some people like me are a bit slow. But thank you very much I solved itEmblazonment
S
1

you can do this whenever you are in a thread:

msgToServer.post(new Runnable() {
    public void run() {
        msgToServer.setText("your text here");
    }
}
Sublingual answered 23/2, 2011 at 21:28 Comment(1)
I think you misunderstood me. But I solved it using a handler. Thank you for your answer.Emblazonment
B
0

Your problem is that there are certain interactions that you can only do on the UI thread. This is one of them.

Looks like you might want to use AsyncTask

http://developer.android.com/reference/android/os/AsyncTask.html

Basically you could make your Runnable an AsyncTask instead and do the setText in onProgressUpdate, which gets run on the UIThread.

Bunker answered 23/2, 2011 at 21:29 Comment(1)
I have looked into it, but nothing on that page make sense to me. Is there some place I could look for an example for AsyncTask? (I solved it using handler instead) Thank you!Emblazonment
K
0

If you would like to use an AsyncTask to run in the background this is how I would do it:

public class UpdateTextProgress_Task extends AsyncTask<Void,String,Void> {
    Socket socket;
    EditText chatFromServer;    

    UpdateTextProgress_Task(EditText chatFromServer, Socket socket){
            this.socket = socket;
            this.chatFromServer = chatFromServer;
}

    @Override
    protected Void doInBackground(Void... params) {
       try {
               BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
               String str = "FIRSTMESSAGEFROMSERVER";

               while(true){
                    if (str.equals("FIRSTMESSAGEFROMSERVER")){
                         str = in.readLine();
                    }
                    else{
                         str = str + "\n" + in.readLine();
                    }
                    Log.e("TCP", "got the message: " + str);

              publishProgress(str); // calls the onProgressUpdate method
          }catch (IOException e) {
        Log.e("UpdateTextProgress", e.getMessage());
    }
    return null;
}

@Override
protected void onProgressUpdate(String... progress) {
    chatFromServer.setText(progress[0]); //now we are on the UI thread so we can update our EditText

}

@Override
protected void onPostExecute(Void result) {
    super.onPostExecute(result);
    Log.e("UpdateTextProgress", "Finished");
}

}

To execute the code:

UpdateTextProgress_Task task = new UpdateTextProgress_Task(chatFromServer,socket);
task.execute();
Kwakiutl answered 11/11, 2013 at 1:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.