30

I am writing an Android Chat App. I am listening for connections and I receive data and I can see it in the Log.d, but whenever I try to update my UI the application crashes.

Code snippet:

private class chatReceiver implements Runnable {
    @Override
    public void run() {
        try {
            skt = new DatagramSocket(Integer.parseInt(Main.prefs.getString("port_number", "5432")));
            DatagramPacket rcvPkt = new DatagramPacket(rcvBuf,rcvBuf.length);
            String ack = "Hello from our SimpleUDPServer";
            byte[] sndBuf = ack.getBytes();
            while (true) {
                Log.d("Server received: " ,"entered loop");
                skt.receive(rcvPkt);
                String rcvMsg = new String(rcvBuf, 0, rcvPkt.getLength(), "UTF-8");
                Log.d("Server received: " ,"receiving" + rcvMsg);
                if (rcvMsg != null) {
                    Log.d("Server received: " ,"not equal null");
                    // I want to update my UI here
                }
                DatagramPacket k = new DatagramPacket(sndBuf, sndBuf.length,
                        rcvPkt.getAddress(), rcvPkt.getPort());
                skt.send(k);
                Log.d("Server sent" ,ack);

            }
        } catch (IOException ex) {
            Log.d("ThreadStart", "Error Starting thread" + ex.getStackTrace());
        }

    }
} 

and to update the UI I do use :

public static void updateUI(Bubble b, View itemView) {

    TextView txt_display_name = (TextView) itemView
            .findViewById(R.id.display_name);
    txt_display_name.setText(b.getDisplay_name());
    TextView txt_chat_body = (TextView) itemView
            .findViewById(R.id.chat_body);
    txt_chat_body.setText(b.getChat_body());
    TextView txt_creation_date = (TextView) itemView
            .findViewById(R.id.creation_date);
    txt_creation_date.setText(b.getCreation_time());
}

The application keeps crashing.

piet.t
  • 11,718
  • 21
  • 43
  • 52
AhmadAssaf
  • 3,556
  • 5
  • 31
  • 42
  • If you are doing some heavy task on Thread and you want to update some UI element (Edittext, Textview etc..) then you have to write that update UI code in runOnUIThread method.. Like this: homeActivity.runOnUiThread(new Runnable() { @Override public void run() { txtview.setText("update with value"); } }); – Nikunjkumar Kapupara Nov 01 '17 at 05:28

4 Answers4

77

You cannot change UI elements from a non-UI thread. Try using runOnUiThread.

runOnUiThread(new Runnable(){
    @Override
    public void run(){
        // change UI elements here
    }
});
user634618
  • 3,573
  • 1
  • 24
  • 17
  • 2
    Using runOnUiThread should only be used for very very short task. If the task taks longer then users will experience a freezing app. – Leukipp Feb 15 '19 at 22:17
61

You cannot touch anything in the UI thread from a background thread, to do that use Handlers, initialize your background thread passing it a Handler object. When data arrives to use the handler to send a message to the UI. In the UI when the message from the background thread comes, just update the Views.

Example Code Snippet :

in the background thread:

if(dataArrives){
    Message msg = handler.obtainMessage();
    msg.what = UPDATE_IMAGE;
    msg.obj = bitmap;
    msg.arg1 = index;
    handler.sendMessage(msg);
}

in the UI thread:

final Handler handler = new Handler(){
  @Override
  public void handleMessage(Message msg) {
    if(msg.what==UPDATE_IMAGE){
      images.get(msg.arg1).setImageBitmap((Bitmap) msg.obj);
    }
    super.handleMessage(msg);
  }
};

and pass the handler to the background thread.

exploitr
  • 843
  • 1
  • 14
  • 27
Franco
  • 7,385
  • 3
  • 27
  • 22
  • 1
    on your last statement, how to pass the handler to the background thread??? The UI thread still freezes during the updating process. – laph Apr 10 '12 at 17:49
  • 1
    @laph That's like the programmer analogon to https://reddit.com/r/restofthefuckingowl – DBX12 May 03 '18 at 13:00
  • 3
    @Franco -1 for leaving out the part about "pass the handler to the background thread" which seems like the most important part – DBX12 May 03 '18 at 13:01
  • If I can't pass the handler, it wouldn't work. It is not 100% possible to transfer the handler to the background thread in all cases. Obviously when you're using Evernote's android-job like me. And, a 49 upvoted answer didn't work for me. – exploitr May 18 '18 at 11:11
  • @DBX12 handler is just a variable, you can just make it a member of your class to access it. – kristianp Sep 11 '18 at 03:44
8

Or just use AsyncTask, it is more useful IMHO.

Olsavage
  • 1,118
  • 8
  • 11
  • 1
    To clarify, you still can't update the UI in `doInBackground` method of an AsyncTask, you need to do it in either the `pre` or `post` execute methods. – Sheharyar Mar 18 '16 at 18:06
  • @Sheharyar very true, facing same issue here. Can't update ui in doInBackground method. – Ahesanali Suthar Jul 13 '16 at 06:59
  • Answering the two comments above - You do the work in `doInBackground()` and update the UI in `OnPostExecute()`. In context of the question - Data from the server will be retrieved from within `doInBackground()` and the retrieved data will be set to the `TextView`'s inside `OnPostExecute()`. – HB. Aug 31 '19 at 08:05
  • This class was deprecated in API level 30. Use the standard java.util.concurrent or Kotlin concurrency utilities instead. – wschopohl Mar 14 '21 at 13:35
0

Use this method to update your UI from the worker thread, here it's explained well.

Note: - If you are using progressbar.setProgress(progress) you can update directly from the worker thread because the ProgressBar internally call setProgress() method using Handler.

exploitr
  • 843
  • 1
  • 14
  • 27
Bijay
  • 11
  • 3