Android How Do I Call RecyclerVIew notifyDataSetChanged() after ThreadPoolExecutor finishes? - java

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.

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:
Use Handler, connected to main looper:
Handler handler = new Handler(getMainLooper());
handler.post(new Runnable() {
#Override
public void run() {
notifyDataSetChanged();
}
});
Use "runOnUiThread" that you've mentioned:
runOnUiThread(new Runnable() {
#Override
public void run() {
notifyDataSetChanged();
}
});
Use the "post" method of your UI-View (RecyclerView, for example):
yourRecyclerView.post(new Runnable() {
#Override
public void run() {
notifyDataSetChanged();
}
});

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.

Related

Repeat Volley Request Every x-seconds Java

Alright - Before you say this is a duplicate, I've looked over every stack overflow article I can find, and none of them work and/or answer the question properly/simply. All I need is to repeat a function with a volley request inside of it every x-seconds.
Basically, I have a fairly simple Volley request inside a function, which works absolutely perfectly on one call.
Volley function:
private void SetBusPositions() {
TextView textE = (TextView) findViewById(R.id.FirstInfo);
RequestQueue Queue = ServerRequestsQueue.getInstance(context.getApplicationContext()).getRequestQueue();
int SendMethod = Request.Method.GET;
String ServerURL = "my url";
JsonArrayRequest JOR = new JsonArrayRequest(SendMethod, ServerURL, null, new Listener<JSONArray>() {
#Override
public void onResponse(JSONArray resp) {
textE.setText(resp.toString());
System.out.println("Response is: " + resp.toString());
//for each object in JSON Array
for (int i = 0; i < resp.length(); i++) {
}
}
}, new Response.ErrorListener() {
public void onErrorResponse(VolleyError error) {
//process
}
});
Queue.add(JOR);
}
I just want to call this function periodically, to receive data from the server and update my bus-position data. There has to be a fairly simple way to do this? I know I must be missing something, but none of the other answers seem to help.
Also, as I'm using Google maps, my class is already extending FragmentActivity. I've seen methods that extend Runnable to get this working -- but my Java is a bit rusty here. I've been doing too much JS.
private final class RepeatedOperation extends AsyncTask<Map, Void, String> {
#Override
protected String doInBackground(Map... params) {
SetBusPosition(params[0]);
}
#Override
protected void onPostExecute(String result) {
}
}
private void callAsynchronousTask() {
final Handler handler = new Handler();
Timer timer = new Timer();
TimerTask doAsynchronousTask = new TimerTask() {
#Override
public void run() {
handler.post(new Runnable() {
public void run() {
try {
RepeatedOperation performBackgroundTask = new RepeatedOperation();
// RepeatedOperation this class is the class that extends AsyncTask
performBackgroundTask.execute(map);
} catch (Exception e) {
// TODO Auto-generated catch block
}
}
});
}
};
timer.schedule(doAsynchronousTask, 0, 50000); //execute in every 50000 ms
}
Try this out and add this in the same scope as SetBusPosition()

Handlers don't end after calling removeCallbacksAndMessages(null)

I have two handlers. Handler in a handler. Both of them are in a for-loop.
The overview is something like this,
for{
handler.postDelayed(runnableA{
for{
handler2.postDelayed(runnableB{
function();
}, 3000);
}
}, 1000);
}
I wanted to end handlers' work at any time when the user clicks back button. So, I created two Runnable Classes so that I can use something like runnableA.removellbacksAndMessages(null).
Handler messageHandler;
Handler countDownHandler;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toast.makeText(this, "Start Play in 5 seconds", Toast.LENGTH_SHORT).show();
countDownHandler = new Handler();
for (int i = 7; i >= 0; --i) {
final int idx = i;
Runnable countRunnable = new CountRunnable(idx, countDownView);
countDownHandler.postDelayed(countRunnable, 1000 * (7 - i));
}
}
And this is Runnable Classes.
public class CountRunnable implements Runnable {
int idx;
TextView countDownView;
public CountRunnable(int idx, TextView countDownView) {
this.idx = idx;
this.countDownView = countDownView;
}
#Override
public void run() {
int messageSize = messageItems.size();
for (int j = 0; j < messageSize; j++) {
final int jdx = j;
messageHandler = new Handler();
Runnable messageRunnable = new MessageRunnable(jdx);
messageHandler.postDelayed(messageRunnable, 3000 * jdx);
}
}
}
class MessageRunnable implements Runnable {
int jdx;
public MessageRunnable(int jdx) {
this.jdx = jdx;
}
#Override
public void run() {
addMessageView(messageItems.get(jdx));
}
}
This is onBackPressed():
#Override
public void onBackPressed() {
super.onBackPressed();
Toast.makeText(getApplicationContext(), "All Work Ended.", Toast.LENGTH_SHORT).show();
scrollFlag = true;
try {
messageHandler.removeCallbacksAndMessages(null);
} catch (Exception e) {
Log.d(TAG, "messageHandler never used");
e.printStackTrace();
}
try {
countDownHandler.removeCallbacksAndMessages(null);
} catch (Exception e) {
e.printStackTrace();
}
}
public void addMessageView(String message){
try{
mTextView.setText(message);
}catch(Exception e){
Toast.makeText(getApplicationContext(), "Abnormal End", Toast.LENGTH_SHORT).show();
}
}
But, I keep getting errors because the activity already ended but the handlers can't find the activity. So, Abnormal End Toast message shows as many as the size of inner for loop.
I can ignore this if I don't use the Toast message, but I am afraid of Memory leak or Bad formed Program or something like that.
How can I fix this problem?
The main problem is that you are creating n numbers of CountRunnables and m number MessageRunnables. Despite creating more than one numbers of handlers you are removing callbacks only for the latest-created Hanlder.
Here's what you should do:
Keep a reference of all the Handlers and Runnables and call messageHandler.removeCallbacksAndMessages(null); and countDownHandler.removeCallbacksAndMessages(null); on all of them.

My UI is blinking and whenever i try to resize the imageview dynamically, the image disappear

#SuppressWarnings({ "rawtypes" })
public void addAttendance(ArrayList<Properties> attendanceusers) {
//tl.removeView(tr);
tl.removeAllViews();
//addHeaderAttendance();
ctr=0;
for (Iterator i = attendanceusers.iterator(); i.hasNext();) {
Properties p = (Properties) i.next();
property_list.add(p);
/** Create a TableRow dynamically **/
tr = new TableRow(this);
picurl=p.getPic();
profile = new ImageView(this);
profile.setPadding(20,50,20,50);
new AsyncTask<Void, Void, Void>() {
#Override
protected Void doInBackground(Void... params) {
try {
InputStream in = new URL(picurl).openStream();
bmp = BitmapFactory.decodeStream(in);
} catch (Exception e) {
// log error
}
return null;
}
#Override
protected void onPostExecute(Void result) {
if (bmp != null)
profile.setImageBitmap(bmp);
}
}.execute();
profile.setOnClickListener(this);
//myButton.setPadding(5, 5, 5, 5);
Ll = new LinearLayout(this);
params = new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT,
LayoutParams.WRAP_CONTENT);
params.setMargins(0, 0, 0, 0);
Ll.setPadding(0, 0, 20, 0);
Ll.addView(profile,params);
tr.addView((View)Ll);
ctr++;
// Add the TableRow to the TableLayout
tl.addView(tr, new TableLayout.LayoutParams(
LayoutParams.FILL_PARENT,
LayoutParams.WRAP_CONTENT));
}
}
The code above is the dynamic design. There is an image there wherein i put images on my android screen that i get from my database using web service. The problem is the images are blinking and also it is too big. I think it is blinking because i am creating a thread within a thread but im still confused on how to fix it. My async task there is how i get the image.
public ArrayList<Properties> attendanceUse(String result) {
ArrayList<Properties> attendanceusers = new ArrayList<Properties>();
try {
JSONArray jArray = new JSONArray(result);
for (int i = 0; i < jArray.length(); i++) {
JSONObject json_data = jArray.getJSONObject(i);
Properties user = new Properties();
user.setPic(json_data.getString("student_ImgUrl"));
attendanceusers.add(user);
//attendanceusers.get(index)
}
} catch (JSONException e) {
Log.e("log_tag", "Error parsing data " + e.toString());
}
return attendanceusers;
}
The code above is how i get my image in connection with a query on php inside my htdocs. The student_imgurl is the column i select from my mysql database to get the url.
private void update()
{
final Handler handler = new Handler();
Timer timer = new Timer();
String splithis;
splithis=mySpinner.getSelectedItem().toString();
splited = splithis.split(" ");
course = splited[0];
room = splited[1];
sections = splited[2];
TimerTask task = new TimerTask() {
#Override
public void run() {
data = bw.getAttendanceFromDB(term, course,sections,day,room);
handler.post(new Runnable() {
public void run() {
try {
//asyntask class ito
ArrayList<Properties> attendanceusers = attendanceUse(data);
addAttendance(attendanceusers);
} catch (Exception e) {
// error, do something
}
}//run
});//handler mo
}//runmo
};//timer
timer.schedule(task, 0,1000); //timer
}
This is my update void. It is on a timer so that the images will update in real time.
mySpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> arg0, View arg1,
int arg2, long arg3) {
data ="";
new Thread(new Runnable() {
public void run() {
runOnUiThread(new Runnable() {
#Override
public void run() {
update();
}
});
}
}).start();
}
#Override
public void onNothingSelected(AdapterView<?> arg0) {
// TODO Auto-generated method stub
return;
}
});
The code above is my code inside my Oncreate. i have a spinner/dropdown wherein they will choose course-room-section and then whenver they picked an item, the images of the students in that course-room-section will be displayed.
What i want is to stop the blinking of my UI and to resize the imageview.
My thoughts are that this could be better done by replacing your void update() method with an AsyncTask<Void, Properties, Void> class to pull in the attendee data from the database and also download the profile image at the same time in its doInBackground() method. As each attendee is loaded you can update your UI via the publishProgress(Properties) and onProgressUpdate(Properties[]) methods. This should result in a less janky UI update.
Bear in mind that if you have multiple AsyncTasks running at the same time you should execute them on the AsyncTask.THREAD_POOL_EXECUTOR to allow them to run in parallel (the default executor is AsyncTask.SERIAL_EXECUTOR)
As to the image size problems, it may be you don't appear set the correct ImageView.ScaleType on your view to get it to fit properly. I expect it should be ImageView.ScaleType.CENTER_INSIDE.

How to use "runOnUiThread(runnable)" inside static method?

I'm writing code in two different classes. The first one runs IOIO Thread which reads pins status of an IOIO board; when this thread is running, it will update the several TextViews which are on the other class (Tab3Activity.java).
I called the method to update the UI just like the code below.
Tab3Activity.setText(index,"string here");
the setText() above need to be static otherwise it gives an err
Cannot make a static reference to the non-static method setText(int, String) from the type Tab3Activity
The problem is on the Tab3Activity.java.
public static void setText(final int idx,final String str) {
runOnUiThread(new Runnable() {
#Override
public void run() {
_textview[idx].setText(str);
}
});
}
the runOnUiThread above gives an err.
Cannot make a static reference to the non-static method runOnUiThread(Runnable) from the type Activity
This is the IOIO Thread code written in Globalioio.java, i'm trying to update the UI on the Tab3Activity.java. Look at the Loop() method.
class Looper extends BaseIOIOLooper {
#Override
public void setup() throws ConnectionLostException {
//setup DigitalOutputs, AnalogInputs etc here.
if(Tab2Activity.isOpened==true){
led_ = ioio_.openDigitalOutput(0, true);
pwm1S = ioio_.openPwmOutput(10, 100);
pwm1S.setDutyCycle((float)Tab2Activity.pwm1Speed.getProgress()/100);
pwm1Move = ioio_.openDigitalOutput(11, false);
pwm2S = ioio_.openPwmOutput(12, 100);
pwm2S.setDutyCycle((float)Tab2Activity.pwm2Speed.getProgress()/100);
pwm2Move = ioio_.openDigitalOutput(13, false);
pwmSrvo1 = ioio_.openPwmOutput(26, 100);
pwmSrvo1.setDutyCycle((float)Tab2Activity.servo1.getProgress()/100);
pwmSrvo2 = ioio_.openPwmOutput(27, 100);
pwmSrvo2.setDutyCycle((float)Tab2Activity.servo2.getProgress()/100);
}
if(Tab3Activity.isOpened==true){
sensor1 = ioio_.openAnalogInput(41);
sensor2 = ioio_.openAnalogInput(42);
for(int i = 0;i<30;i++){
dInput[i] = ioio_.openDigitalInput(DIGITAL_SENSOR_PIN[i]);
}
for(int i = 0; i<10;i++){
aInput[i] = ioio_.openAnalogInput(ANALOG_SENSOR_PIN[i]);
}
}
connStatus=true;
}
#Override
public void loop() throws ConnectionLostException {
try {
if(Tab3Activity.slideDrawer2.isOpened()==true){
final float range1 = (float)(2914/(sensor1.read() * 675.18+5))-1;
Tab3Activity.setSeekBarSensor(0,(int) (range1));
Tab3Activity.setTextSensor(0,Float.toString((range1)));
final float range2 = (float)(2914/(sensor2.read() * 675.18+5))-1;
Tab3Activity.setSeekBarSensor(1,(int) (range2));
Tab3Activity.setTextSensor(1,Float.toString(range2));
}
if(Tab3Activity.slideDrawer1.isOpened()==true){
if(Tab3Activity.pinsGroup==0){
int idx =0;
for(int i = 0;i<10;i++){
final boolean readingD = dInput[i].read();
if(readingD==true){
Tab3Activity.setSeekBar(idx,(int) (100));
}else{
Tab3Activity.setSeekBar(idx,(int) (0));
}
Tab3Activity.setText(idx,Boolean.toString(readingD));
idx++;
}
}else if(Tab3Activity.pinsGroup==1){
int idx =0;
for(int i = 10;i<20;i++){
final boolean readingD = dInput[i].read();
if(readingD==true){
Tab3Activity.setSeekBar(idx,(int) (100));
}else{
Tab3Activity.setSeekBar(idx,(int) (0));
}
Tab3Activity.setText(idx,Boolean.toString(readingD));
idx++;
}
}else if(Tab3Activity.pinsGroup==2){
int idx=0;
for(int i = 20;i<30;i++){
final boolean readingD = dInput[i].read();
if(readingD==true){
Tab3Activity.setSeekBar(idx,(int) (100));
}else{
Tab3Activity.setSeekBar(idx,(int) (0));
}
Tab3Activity.setText(idx,Boolean.toString(readingD));
idx++;
}
}else if(Tab3Activity.pinsGroup==3){
int idx=0;
for(int i = 0;i<10;i++){
final float readingA = aInput[i].read();
Tab3Activity.setSeekBar(idx,(int) (readingA * 100));
Tab3Activity.setText(idx,Float.toString((readingA * 100)));
idx++;
}
}
}
Thread.sleep(10);
} catch (InterruptedException e) {
ioio_.disconnect();
} catch (ConnectionLostException e) {
throw e;
}
}
}
#Override
public IOIOLooper createIOIOLooper(String arg0, Object arg1) {
// TODO Auto-generated method stub
return new Looper();
}
Is there any alternative to do this?
please give the simple one, i'm quite new to android. Thanks in advance
If this thread is started from the same activity
then you can pass the reference of the activity to the thread, and remove static from that method.
YourThread thread = new YourThread(yourActivity);
thread.start();
//YourThread
public class YourThread extends Thread
{
Tab3Activity activity;
public YourThread(Tab3Activity activity)
{
Tab3Activity.activity = activity;
}
...
activity.setText(index,"string here");
...
}
Note: Make sure your activity has android:configChanges="orientation|keyboardHidden|screenSize". Otherwise as you rotate your devices there will be a new instance of acitivity started.
And if your activity is not starting that thread
then you should not try to access the activity directly through a static method.
If you are sure about your implementation and if it does not lead to a memory leak or crash then try this
Create a static MainLooper Handler in your activity or anywhere.
public static Handler UIHandler = new Handler(Looper.getMainLooper());
now you can use this handler to run on ui thread.
public static void setText(final int idx,final String str) {
UIHandler.post(new Runnable() {
#Override
public void run() {
_textview[idx].setText(str);
}
});
}
Yes there is,
you are coupling between your Thread and the Activity which is not a good design ,
instead use Intent when the Thread finish the I/O fire Intent and catch this inside the activity

How can I increase the final integer variable?

Eclipse is offering final but I can't increase the i variable.
#Override
public void onClick(View v) {
final TextView tv = (TextView) findViewById(R.id.tvSayac);
int i = 1;
do {
try {
new Thread(new Runnable() {
public void run() {
tv.post(new Runnable() {
public void run() {
tv.setText(Integer.toString(i));
}
});
}
});
i++;
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} while (i < 16);
}
A final is an entity that can not be changed after it is initialized.
Final (Java)
What you could do is create a variable within the scope of the do/while loop that is final with the value of i and send that into the function.
The easiest solution here is to create a class:
public class FinalCounter {
private int val;
public FinalCounter(int intialVal) {
val=intialVal;
}
public void increment(){
val++;
}
public void decrement(){
val--;
}
public int getVal(){
return val;
}
public static void main(String[] arg){
final FinalCounter test = new FinalCounter(0);
test.increment(); // 1
test.increment(); // 2
test.increment(); // 3
test.increment(); // 4
test.increment(); // 5
test.decrement(); // 4
System.out.println(test.getVal()); // prints 4
}
}
I think it is possible to create a local copy of the variable i. Try this:
#Override
public void onClick(View v) {
final TextView tv = (TextView) findViewById(R.id.tvSayac);
int i = 1;
do {
final int localCopy = i; // Create here a final copy of i
try {
new Thread(new Runnable() {
public void run() {
tv.post(new Runnable() {
public void run() {
// use here the copy
tv.setText(Integer.toString(localCopy));
}
});
}
}).start(); // Don't forget to start the Thread!
i++;
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} while (i < 16);
}
By creating a final local copy:
the compiler won't complain anymore
because of Java copies by value, you will only increase i and not localCopy.
I suppose you want to start the Thread as well...
EDIT: Indeed, you were right. You have to create the local final copy inside the loop. Check the new code.
A final variable can only be initialized once not necessarily when you are defining it. It can be set any time within the constructor , but only once. In your case when you are incrementing i using i++, you are trying to assign the incremented value to i again which is not allowed.
You could create a counter class like that and increment it. This way, the reference of the Counter object could be final but you could still set its value ?
What I did was add a:
private int i;
Before this:
#Override
public void onClick(View v) {
final TextView tv = (TextView) findViewById(R.id.tvSayac);
i = 1;
do {
try {
new Thread(new Runnable() {
public void run() {
tv.post(new Runnable() {
public void run() {
tv.setText(Integer.toString(i));
}
});
}
});
i++;
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} while (i < 16);
}
And you'll be able to use your variable as usual after that, without having to mark it as final.

Categories

Resources