1

I am learning ThreadPoolExecutor by following this tutorial. To demonstrate its usage, I made a simple android project, it has a recyclerview that will show some Strings. Initially, the array of Strings(String[] myDataset = new String[10]) has 10 nulls. My threadPoolExecutor generates some random strings and fills up the array. So whenever a new String is generated and placed inside the array, I should call notifyDataSetChanged() so that the recyclerView will update and show those random Strings.

the problem

I don't understand how to call notifyDataSetChanged() and so I am pinned down. I got this exception:

Caused by: android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

Since I know AsyncTask, I understand that this error means I cannot call this method in background thread but I have to call it in main thread/ui thread ( so in AsyncTask, it would look like this:

@Override
    protected void onPostExecute(String result) {
      weakReference.get().notifyDataSetChanged(); // something like that
    }

). I need it's ThreadPoolExecutor counterpart. I did google and found this but I am not sure how to do this.

The necessary code segment is given below:

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
    private String[] myDataset;
    private final ThreadPoolExecutor threadPoolExecutor;
    private Future future;

    private Runnable getRunnable(final int i) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                String randomString = MyAdapter.getRandomString(i)+" "+i; // <--create random string
                Log.e(TAG, randomString);
                myDataset[i] = randomString;
                try { Thread.sleep(3000); }
                catch (InterruptedException e) { e.printStackTrace(); }
            }
        };
        return runnable;
    }

    public void doSomeBackgroundWork(){
        Runnable[] commands = new Runnable[myDataset.length];
        for(int i1=0; i1<commands.length; i1++) {
            final int j1 = i1;
            commands[j1] = () -> {
                String randomString = MyAdapter.getRandomString(j1)+" "+j1;
                Log.e(TAG, randomString);
                myDataset[j1] = randomString;
                try { Thread.sleep(3000); }
                catch (InterruptedException e) { e.printStackTrace(); }

// notifyDataSetChanged();           // <-------- Error. Where/How should I call it?
            };

            threadPoolExecutor.execute(commands[j1]);
        }
    }

public MyAdapter(String[] myDataset) {
        this.myDataset = myDataset;  // {null, null, ... ...}
        this.threadPoolExecutor = DefaultExecutorSupplier.getInstance().forBackgroundTasks(); // returns new ThreadPoolExecutor( ... parameters... );
//        future[i] = threadPoolExecutor.submit(command); future[i].cancel(true); use it like this
        doSomeBackgroundWork();
    }

// ... the rest of the recyclerview related code

}

Could anyone help me? Thank you for reading.

Qazi Fahim Farhan
  • 2,066
  • 1
  • 14
  • 26

2 Answers2

2

There is a Handler class under the hood in all cases where you need to communicate to UIThread from the another Thread (AsyncTask use it as well).

Some of possible choices:

  1. Use Handler, connected to main looper:

    Handler handler = new Handler(getMainLooper()); handler.post(new Runnable() { @Override public void run() { notifyDataSetChanged(); } });

  2. Use "runOnUiThread" that you've mentioned:

    runOnUiThread(new Runnable() { @Override public void run() { notifyDataSetChanged(); } });

  3. Use the "post" method of your UI-View (RecyclerView, for example):

    yourRecyclerView.post(new Runnable() { @Override public void run() { notifyDataSetChanged(); } });

sergiy tikhonov
  • 4,961
  • 1
  • 10
  • 27
0

In case anyone needs, here is what I did based on sergiy-tikhonov's answer.

    public void doSomeBackgroundWork(){
        Runnable[] commands = new Runnable[myDataset.length];
        for(int i1=0; i1<commands.length; i1++) {
            final int j1 = i1;
            commands[j1] = () -> {
                String randomString = MyAdapter.getRandomString(j1)+" "+j1;
                Log.e(TAG, randomString);
                myDataset[j1] = randomString;
                try { Thread.sleep(3000); }
                catch (InterruptedException e) { e.printStackTrace(); }
                // notifyDataSetChanged();           // <-------- Error
                recyclerViewWeakReference.get().post(new Runnable() {  // <---- this is the change
                       @Override
                       public void run() {
                           notifyDataSetChanged();
                       }
                });
            };

            threadPoolExecutor.execute(commands[j1]);
        }
    }

So as you can see, I tried the third option. First I created a WeakReference<RecyclerView> recyclerViewWeakReference = new WeakReference<RecyclerView>(myRecyclerView) in the parent fragment (or activity if you are using that). Then I passed the weak reference into MyAdapter. I used weakReference because that is what you do with AsyncTask,so my instincts alerted me to do so. I hope this is helpful.

Qazi Fahim Farhan
  • 2,066
  • 1
  • 14
  • 26