3

I've seen a few questions on here asking similar questions, but I've not yet seen a suitable answer. Many people have asked how to update the UI from a thread, but they're almost always in the same class as the UI.

What I'm trying to do is update the UI from a thread which has been created in another class. I've seen all of the suggestions, such as async, handlers, runnable, etc... but I've having real trouble implementing them in separate classes.

I'm trying to keep my UI class minimal and only deal with interactions with the GUI, such as when a user presses a button. Now, I've created a new thread, in a new class, which connects to a Bluetooth device, but I then want to change a button in the UI thread from being a 'connect' button to a 'disconnect' button (i.e. change the button from creating the Bluetooth socket to closing it).

What is the general way to do this? Am I thinking of this all wrong and should have everything in one class? What is the correct way to interact between the 'main' UI class and other classes/threads?

Ideally I want to be able to do other UI interactions, so some solution which allows other UI changes outside of the UI class would be great!

ritchie888
  • 583
  • 3
  • 12
  • 27

3 Answers3

8

What I'm trying to do is update the UI from a thread which has been created in another class. I've seen all of the suggestions, such as async, handlers, runnable, etc... but I've having real trouble implementing them in separate classes.

Generally for your goal i recommend to you use:

I don't think that its too tricky. Absolutely not. If you have it as separated class(es) and not as inner class(es) in some Activity class so i recommend to use constructor where you will pass context, widgets, generally whatever you want and then in correct methods(which allows UI update) update your UI.

I'm doing it because i like when i have clean classes(so UI class have only UI implementations and logic is positioned separately).

Example:

public class TaskExample extends AsyncTask<Void, Integer, Void> {

   private Context c;
   private Button b;

   public TaskExample(Context c, Button b) {
      this.c = c;
      this.b = b;
   }

   protected Void doInBackground(Void... params) {
      // some work
      if (isSomethingConnected) {
         publishProgress(Constants.IS_CONNECTED);
      }
      return null;
   }

   public void onProgressUpdate(Integer... params) {
       switch (params[0]) {
          case Constants.IS_CONNECTED:
             b.setText("Connected");
          break;
          case Constants.ANOTHER_CONSTANT:
             // another work
          break;
       }
   }  
}

Usage:

public class Main extends Activity implements View.OnClickListener {

   private Button b;

   public void onCreate(Bundle b) {
      super.onCreate(b);
      // initialise widgets and set listeners to appropriate widgets
   }

   public void onClick(View v) {
      switch(v.getId()) {
         case R.id.connectBtn:
            startWorker();
         break;
      }
   }

   private void startWorker() {
      TaskExample te = new TaskExample(this, b);
      te.execute();
   }
}
Simon Dorociak
  • 33,374
  • 10
  • 68
  • 106
  • Thank you for this. Two things come to mind. I have tried playing around with AsyncTask with various examples I've come across, but I have seen some information on it where it states AsyncTask has to be called within the same class as the UI. Is this correct or can it be called elsewhere? Secondly, I haven't been successful attempting to pass the context across like you've suggested. I created a static context variable in the main UI thread and then attempted to access it in the new class, but no long. By any chance would you have some example code or know where I can find some? – ritchie888 Feb 20 '13 at 17:42
  • @ritchie888 i wrote example of `AsyncTask` here. – Simon Dorociak Feb 20 '13 at 17:44
  • Was there suppose to be a link or something there? As it's not displaying. Cheers. – ritchie888 Feb 20 '13 at 17:46
  • @ritchie888 no here i wrote especially for you example. click on F5 in browser. – Simon Dorociak Feb 20 '13 at 17:49
  • Oh yes, so you did. Thanks very much for this. I'll try it out now. – ritchie888 Feb 20 '13 at 18:01
  • Thank you! I did some editing and made a simple button counter. Every time you pressed the button the number incremented up and changed the button text. I also added a toast so I could check the use of context worked too. Out of curiosity, can you only pass arrays over from doInBackground to onProgressUpdate? I would only normally want to pass a variable across. Using params[0] is fine, but seems a little unnecessary. Am I right in thinking that doInBackground is the thread which is performing the parallel calculations, and onProgressUpdate is the 'gateway' function that allows UI changes? – ritchie888 Feb 20 '13 at 18:30
  • 1
    No you can pass only arrays, simply AsyncTask is designated in this way(sometimes you need to pass more than one value from doInBackground, sometimes application logic needs it so if AsyncTask will pass only single value, it is not enough for all possible cases). doInBackground method runs in background(in background thread) and is generally designed for calculating long running operations and yes onProgressUpdate is method that runs on UI Thread and you can imagine it like "way" between background and UI. – Simon Dorociak Feb 20 '13 at 18:52
  • AsyncTask: "This class was deprecated in API level 30. Use the standard java.util.concurrent or Kotlin concurrency utilities instead." – cesargastonec Jul 14 '20 at 05:28
2

There are a couple of options. If you have access to the View you are changing and simply need to force a refresh, you can use View.postInvalidate() from any thread. If you need more complex operations, such as changing the text of a button, you should use runOnUIThread, which requires access to the Activity context. This should be simple to get - just add it as a parameter for your custom Object's constructor. With this context, you can do something like this:

activityContext.runOnUiThread(new Runnable(){
    @Override
    public void run() {
        myButton.setText("disconnect");
    }
});
Phil
  • 35,852
  • 23
  • 123
  • 164
0

Use the runOnUiThread(Runnable) method to run something on the Main thread and call the ClassName.View.invalidate() method if it is a view or just make a public method in you're Target class which handles the refreshing of the UI.

AbdulHannan
  • 358
  • 1
  • 15