I have a class implementing runnable that made some task, then update some views:
public class DownloadPics implements Runnable {
#Override
public void run() {
//retrieve data
fillView(data);
}
public void refreshView(ArrayList<Object> new_pics) {
LinearLayout linearLayout = (LinearLayout) ((Storico)context).findViewById(R.id.linearInsideScroll);
View new_imageView = inflater.inflate(R.layout.history_element,
null, false);
//Build fill view
linearLayout.addView(new_imageView);
}
}
i Obtain an error when i call addView on refreshView method:
Only the original thread that created a view hierarchy can touch its views.
What's wrong?
Only the original thread that created a view hierarchy can touch its
views.
Updating ui from a background thread.
You cannot update ui from a background thread. You can update ui from the ui thread.
Use AsyncTask makes it easier. You can do background computation in doInBackground and update ui in onPostExecute
Use this method runOnUiThread(Runnable action)
EDIT
Use ASyncTask along with runOnUiThread
class download extends AsyncTask<Void, Void, Void>
{
String message = "";
ProgressDialog dialog = new ProgressDialog(v.getContext());
#Override
protected void onPreExecute()
{
// what to do before background task
dialog.setTitle("Downloading");
dialog.setIndeterminate(true);
dialog.setMessage(message);
dialog.setCancelable(false);
dialog.show();
}
void updateItem(int i)
{
class UpdateItem implements Runnable
{
int pos;
UpdateItem(int i) { pos = i; }
public void run()
{
dialog.setMessage(message);
}
}
runOnUiThread(new UpdateItem(i));
}
#Override
protected Void doInBackground(Void... params) {
downloadStuff();
i++
updateItem(i);
}
#Override
protected void onPostExecute(Boolean result) {
// what to do when background task is completed
dialog.dismiss();
};
};
new download().execute((Void[]) null);
You need a reference from a Handler that was created on the main thread (be sure that it is created on the main thead).
Handler handler = new Handler();
In the run() method of DownloadPics you would then use:
handler.post(new Runnable(){
public void run(){
//update UI on main thread
}
}
Related
I see a similar question here
AnimationDrawable in RecyclerView item not animating on notifyDataSetChanged.
But couldn't resolve this issue or fix. I think its because notifyDataSetChanged call multiple times to update UI. While I comment or notification change call for the position it works fine.
I have three items in the recycled view.
Progress bar
Text view
Button
I need to call an API on button click and increment the textview value. while API calls I need to show the progress bar and hide while API finish
Button click as follow,
holder.image_add_overlay.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
holder.progressBar2.setVisibility(View.VISIBLE);
UpdateProductItem(holder, position);
}
});
private void UpdateProductItem(ShoppinglistAdapter.ViewHolder holder, int position) {
try {
Boolean value = CartFunctions.UpdateCartPostValue(cartAdd, mAPIService);
if (value) {
holder.text_item_cart_count.setText("" + holder.cart_count_value);
// notifyDataSetChanged();
holder.progressBar2.setVisibility(View.GONE);
Task task = new Task();
task.execute();
Toast.makeText(context, context.getResources().getString(R.string.cart_update_toast_message), Toast.LENGTH_SHORT).show();
} else {
holder.cart_count_value = holder.cart_count_value - 1;
}
} catch (IOException e) {
e.printStackTrace();
}
}
I also try the
#SuppressLint("StaticFieldLeak")
public class Task extends AsyncTask<String, Integer, Integer> {
#Override
protected Integer doInBackground(String... strings) {
Log.i("test", "doInBackground");
// notifyDataSetChanged();
return null;
}
#Override
protected void onPostExecute(Integer integer) {
Log.i("test", "onPostExecute");
notifyDataSetChanged();
super.onPostExecute(integer);
}
}
And call the this using
Task task = new Task();
//task.doInBackground();
task.onPostExecute(0);
May the 2 item in recyclerview are same, so the count is shown to both become same. so i need notifyDataSetChanged() instead of notifyItemChanged(position);
Did any guys know the solution?
doInBackground() method works in a separate thread, not even on UI thread.
Notify your adapter on UI thread, From AsyncTask : onPreExecute() & onPostExecute() method works on UI thread.
public class Task extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... params) {
return null;
}
#Override
protected void onPostExecute(Void result) {
adapter.notifyDataSetChanged();
}
}
Call your Task using,
Task task = new Task();
task.execute();
Refer AsynkTask for more details.
I got a problem with timer. After 2 sec instead of this action I am getting an error.
public class MainActivity extends AppCompatActivity {
private TextView tv;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
tv = (TextView)findViewById(R.id.tv);
Timer timer=new Timer();
TimerTask timerTask = new TimerTask() {
#Override
public void run() {
tv.setText("why?");
}
};
timer.schedule(timerTask,2000);
}
}
How to fix this?
You cannot run these from non main thread
#Override
public void run() {
tv.setText("why?");
}
You have to use runOnUiThread
#Override
public void run() {
runOnUiThread(new Runnable(){
#Override
public void run() {
tv.setText("why?");
}});
}
}
Or use post method of View like
#Override
public void run() {
tv.post(new Runnable(){
#Override
public void run() {
tv.setText("why?");
}});
}
}
I know it looks like weird, but if you write it using lambdas its much better and concise
timer.schedule(()->{tv.post(()->{setText("why?");}},2000);
You cannot update UI from inside a thread. Update UI from main thread.
Create a handler and send message to the handler after your work has been completed and update the UIthere.
Handler handler = new Handler()
{
#Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
// Update UI
}
};
and send message to handler using handler.sendMessage()
I want the countdowntimer to be on a separate thread and for it to update the UI on each tick. Every time I click start the app just closes and I get the 'app has stopped' error message.
public class Activity_MultiplayerGame extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity__multiplayer_game);
}
public void btnStart_onClick(View view){
CountDownTimer timer = new CountDownTimer(15000, 1000){
#Override
public void onTick(final long millisUntilFinished) {
runOnUiThread(new Runnable() {
#Override
public void run() {
TextView countdownText = (TextView) findViewById(R.id.countdown);
Integer timeUntilFinished = (int) millisUntilFinished/1000;
countdownText.setText(timeUntilFinished);
}
});
}
#Override
public void onFinish() {
TextView countdownText = (TextView) findViewById(R.id.countdown);
countdownText.setText("Finished");
}
};
timer.start();
}
}
I've made the assumption that creating a CountDownTimer gives it its own thread?
you can use Handler. I wrote a sample for you
this code increase a counter every one second and show and update counter value on a textView.
public class MainActivity extends Activity {
private Handler handler = new Handler();
private TextView textView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.text);
startTimer();
}
int i = 0;
Runnable runnable = new Runnable() {
#Override
public void run() {
i++;
textView.setText("counter:" + i);
startTimer();
}
};
public void startTimer() {
handler.postDelayed(runnable, 1000);
}
public void cancelTimer() {
handler.removeCallbacks(runnable);
}
#Override
protected void onStop() {
super.onStop();
cancelTimer();
}
}
You cannot update UI from other thread than main (ui) thread on android. There are many ways to approach the problem, but I suppose you should start with AsyncTask if you really need another thread. Check doInBackround and onProgressUpdate methods.
If you don't need other thread (and if this is only about the counter you dont') you can check Handler class and postAtTime(Runnable, long), postDelayed(Runnable, long) methods. Or you can make subclass of Handler class and use combination of sendMessageDelayed(android.os.Message, long) and overridden handleMessage().
Here's your problem:
countdownText.setText(timeUntilFinished);
You have passed integer to setText(int) method, which actually takes the string resource id rather than the integer to be displayed. You should use the method setText(String).
Try this:
countdownText.setText(String.valueOf(timeUntilFinished));
This code also work it decrease counter(or increase whatever you want) and show in text view you can also stop or reset counter via click the same button.
public class MainActivity extends AppCompatActivity {
TextView textView;
Button count;
boolean timelapseRunning = true;
int NUM_OF_COUNT=15;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView=(TextView)findViewById(R.id.textview);
count=(Button)findViewById(R.id.count);
runTimer();
}
public void onclick(View view){
if (!timelapseRunning) {
timelapseRunning=true;
} else {
timelapseRunning = false;
}
if(NUM_OF_COUNT==0){
NUM_OF_COUNT=15;
}
}
private void runTimer() {
final Handler handler = new Handler();
handler.post(new Runnable() {
#Override
public void run() {
textView.setText(""+NUM_OF_COUNT);
if (!timelapseRunning && NUM_OF_COUNT>0) {
NUM_OF_COUNT--;
}
handler.postDelayed(this, 1000);
}
});
}
}
The CountDownTimer gives out callbacks on the same thread, in which it was created. So the runOnUiThread in onTick is absolutely not required if the CountDownTimer constructor was called on UI thread.
Also,
TextView countdownText = (TextView) findViewById(R.id.countdown);
being called in onTick method is poor design, as findViewById method consumes considerable processing power.
Finally the actual problem as Nabin Bhandari pointed out is the integer value passed to setText method. You need to wrap it to string.
This code is enough
final TextView countdownText = (TextView) findViewById(R.id.countdown);
CountDownTimer timer = new CountDownTimer(15000, 1000) {
#Override
public void onTick(long millisUntilFinished) {
countdownText.setText(String.valueOf(millisUntilFinished/1000));
}
#Override
public void onFinish() {
countdownText.setText("Finished");
}
};
timer.start();
I'm new to android programming and I recently searched for simple fade-out animation for an object. I've put it in my code, tested it and it was choppy. I checked the logs and saw this :
11-05 19:19:21.319: I/Choreographer(12759): Skipped 40 frames! The application may be doing too much work on its main thread.
Here's my code in the MainActivity class
private MainActivity actMain;
FadeIn fadeClass = new FadeIn();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.view_switcher);
actMain = this;
new Thread() {
#Override
public void run() {
// If there are stories, add them to the table
try {
// code runs in a thread
runOnUiThread(new Runnable() {
#Override
public void run() {
fadeClass.animation(actMain);
}
});
} catch (final Exception ex) {
Log.i("---", "Exception in thread");
}
}
}.start();
}
And from the FadeIn class:
private View view;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void animation(Activity context) {
final View view = context.findViewById(R.id.fadeOutLayer);
Animation fadeOut = new AlphaAnimation(1f, 0f);
fadeOut.setDuration(3000);
fadeOut.setAnimationListener(new AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationRepeat(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
view.setVisibility(View.GONE);
}
});
view.startAnimation(fadeOut);
}
Thank you!
You are creating and executing a new thread in the main thread, and inside of this new thread, you are executing a runOnUiThread, that synchronize with the Main thread and executes its content in it. It makes no sense, because is like execute the content of runOnUiThread in the onCreate.
I recommend that you use the "fadeClass.animation(actMain);" directly inside of onActivityCreated
You can also animate by simply using animate() method for simple animations. Animations like alpha, rotation, scale are available. I am giving an example of translation.
mImageView.animate().translationX(-screenWidth)
.setDuration(ANIMATION_DURATION).withEndAction(new Runnable() {
#Override
public void run() {
mImageView.setImageDrawable(d);
mImageView.setVisibility(View.INVISIBLE);
}
});
Can be done easily on main thread
I'd like to know the limits and how Handlers work in Android.
I can understand why this works exactly as expected:
public class MainActivity extends Activity {
TextView tv;
Handler mHandler;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHandler = new Handler();
tv = new TextView(this);
tv.setText("original text");
setContentView(tv);
new Thread(new Runnable() {
#Override
public void run() {
mHandler.post(new Runnable() {
#Override
public void run() {
tv.setText("changing from instance member handler");
}
});
}
}).start();
}
that code, as expected, changes the TextView without complaining for being on wrong thread. i made my handler on UI thread.
however, what i don't understand is... why does THIS also work?
public class MainActivity extends Activity {
TextView tv;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
tv = new TextView(this);
tv.setText("original text");
setContentView(tv);
new Thread(new Runnable() {
Handler handler = new Handler();
#Override
public void run() {
handler.post(new Runnable() {
#Override
public void run() {
tv.setText("changing from different thread.");
}
});
}
}).start();
}
Two questions:
1) I've made my handler from a non-UI thread, but it still updates the textview without complaining about touching a view outside of UI thread. why is that? Where are my messages actually being posted to?
2) say i have 20 pieces of code like that, where i make a new Handler instance each time, will there ever be a problem with running off of UI thread, or a performance issue?
Note: if i made a thread inside of a thread, and instantiate the handler in that inner inner thread, THEN the TextView can't be touched there.
Only code inside run() is executed in another thread. All the initialization is done in the thread that creates the class. And the Runnable is created entirely inside the ui thread.
If you were to write it like
new Thread(new Runnable() {
#Override
public void run() {
Handler handler = new Handler();
handler.post(new Runnable() {
#Override
public void run() {
tv.setText("changing from different thread.");
}
});
}
}).start();
You would get a crash because there is no looper in the thread.