after this thread, i tried to make a variable status bar with this code:
private int[] loadingElementIDs;
private void initLoadingBar() {
final DisplayMetrics displayMetrics=getResources().getDisplayMetrics();
final float screenWidthInDp = displayMetrics.widthPixels/displayMetrics.density;
final int elementAmount = (int) (Math.floor(screenWidthInDp * 0.5f / 30) * 5);
//set margins
LinearLayout container = (LinearLayout)findViewById(R.id.loading_outer);
...
container.requestLayout();
//declare length
loadingElementIDs = new int[elementAmount];
LayoutParams LLParams = new LayoutParams(0, LayoutParams.MATCH_PARENT);
LLParams.weight = 1f;
LinearLayout element;
for (int i=0; i<elementAmount; i++) {
int id = generateViewId(); //creates unique id
element = new LinearLayout(this);
element.setVisibility(View.INVISIBLE);
element.setLayoutParams(LLParams);
element.setBackgroundDrawable(getResources().getDrawable(R.drawable.loading_inner));
element.setId(id);
element.requestLayout();
container.addView(element);
loadingElementIDs[i] = id;
}
}
this is working fine for me, but now i want to calculate sth with an asynctask and make the elements visible (code within my activity class):
private class PrefetchData extends AsyncTask<Void, Void, Void> {
#Override
protected void onPreExecute() {
super.onPreExecute();
}
#SuppressWarnings("static-access")
#Override
protected Void doInBackground(Void... arg0) {
try {
int step = 0;
float totalSteps = 100f;
while (...) {
step++;
// ...................
//show status
setLoadingStatus( step / totalSteps);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
Intent i = new Intent(SplashScreen.this, MainActivity.class);
startActivity(i);
finish();
}
}
public void setLoadingStatus(float percentage) {
int max = (int) Math.min( Math.floor(percentage * loadingElementIDs.length),
for (int d=0; d<max; d++ ) {
((LinearLayout)findViewById(loadingElementIDs[d])).setVisibility(View.VISIBLE);
LinearLayout el = (LinearLayout)this.findViewById(loadingElementIDs[d]);
el.setVisibility(LinearLayout.VISIBLE);
}
}
And this does not work. if i call setLoadingStatus(20f); from onCreate it works perfectly, but not in the AsyncTask. Of course i do start initLoadingBar(); and new PrefetchData().execute(); in activities onCreate().
do you know what i'm doing wrong?
Use UI thread for update UI components. If you need to update task progress, you can use publishProgress(xxx) and onProgressUpdate(xxx). For more dateils: http://developer.android.com/reference/android/os/AsyncTask.html
I don't know how this got merged, the comment is all borked, but the requested code snippet is below for managing this with a Handler:
define a handler in your activity:
Handler handler = new Handler(){
handleMessage(Message msg)
{
if (msg.what == STATUS)
{
//do something if it's a message form your AsyncTask
}
else
//other messages..
}
};
when creating your AsyncTask, give it your handler. define a constructor to accept this and keep a local reference to it.
new PrefetchData(handler).execute(...);
and then inside your AsyncTask: (STATUS would be a constant setup as the message code.. )
while (...) {
step++;
// ...................
//show status
handler.obtainMessage(STATUS, step / totalSteps).sendToTarget();
}
thank you guys, i solved it with onProgressUpdate. instead of setLoadingStatus i call this:
private class PrefetchData extends AsyncTask<Void, Float, Void> {
....
protected void onProgressUpdate(Float... values) {
for (int d=0; d<Math.min( Math.floor(values[0] * loadingElementIDs.length), loadingElementIDs.length); d++ ) {
LinearLayout el = (LinearLayout)findViewById(loadingElementIDs[d]);
el.setVisibility(LinearLayout.VISIBLE);
}
}
}
You can runOnUiThread method to call Ui functions from any thread
runOnUiThread(new Runnable() {
public void run() {
// some code #3 (Write your code here to run in UI thread)
}
}); // enter code here
Related
I have a problem with showing progress on ProgressBar.
I have function that calls my server with Volley, when return result I have implemented a Callback that return data to my activity.
Something like:
public static void saveData(Data data, final VolleyCallbackJsonObject callback){
...
callback.onSuccessResponse(new JSONObject());
...
}
Supposing that the function is called on cycle like:
for(int i = 1; i<=5; i++){
final int copyIndex = i;
MyClass.saveData(new Data(i), new VolleyCallbackJsonObject() {
#Override
public void onSuccessResponse(JSONObject result) {
callFunctionForShowAndUpdateProgress(copyIndex);
}
})
}
}
I would like show a ProgressBar that show a bar with a progress from 1/5 to 5/5
so I have my Function in my activityClass:
public Class Test extends Activity{
private ProgressBar pb;
public void callFunctionForShowAndUpdateProgress(int index){
if(index == 1){
pb = (ProgressBar) findViewById(R.id.pbLoading);
pb.setMax(5);
pb.setIndeterminate(false);
pb.setVisibility(View.VISIBLE);
}
if(pb != null){
pb.setProgress(index);
}
if(index == 5){
pb.postDelayed(new Runnable() {
#Override
public void run() {
pb.setVisibility(View.GONE);
}
}, 3000);
}
}
}
My problem is that progress bar not set progress, but it show only at the end so with 5/5 and not show the progress step (1/5, 2/5, 3/5, 4/5) and then it will hide after 3 seconds how specified on postDelayed.
So How can I show all the step? what is wrong?
Try to postDelay the method 'callFunctionForShowAndUpdateProgress' in the method 'onSuccessResponse' instead the postDelay you are already making because the delay you are making happens only when i = 5, so technically the progress goes through 2, 3, and 4 but you don't see it.
try this inside 'onSuccessResponse':
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
callFunctionForShowAndUpdateProgress(copyIndex);
}
}, 3000);
By the way, When the method 'callFunctionForShowAndUpdateProgress' is called in the
'onSuccessResponse' the index initially will be 1 and that is fine, the compiler then will go to second if statement
if(pb != null){
pb.setProgress(index);
}
and will execute it because the condition is correct there, it will not cause a problem but leaving it like this is not a good practice, to solve this prevent the compiler from getting into this if statement by adding else.
I hope that helps
Change your cycle calling function as like below and then check,
int copyIndex = 1;
callFunctionForShowAndUpdateProgress(copyIndex);
for(int i = 1; i<=5; i++)
{
MyClass.saveData(new Data(i), new VolleyCallbackJsonObject()
{
#Override
public void onSuccessResponse(JSONObject result)
{
copyIndex++;
callFunctionForShowAndUpdateProgress(copyIndex);
}
})
}
I'm following this tutorial
to learn how to make progress bars. I'm trying to show the progress bar on top of my activity and have it update the activity's table view in the background.
So I created an async task for the dialog that takes a callback:
package com.lib.bookworm;
import android.app.ProgressDialog;
import android.content.Context;
import android.os.AsyncTask;
public class UIThreadProgress extends AsyncTask<Void, Void, Void> {
private UIThreadCallback callback = null;
private ProgressDialog dialog = null;
private int maxValue = 100, incAmount = 1;
private Context context = null;
public UIThreadProgress(Context context, UIThreadCallback callback) {
this.context = context;
this.callback = callback;
}
#Override
protected Void doInBackground(Void... args) {
while(this.callback.condition()) {
this.callback.run();
this.publishProgress();
}
return null;
}
#Override protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
dialog.incrementProgressBy(incAmount);
};
#Override
protected void onPreExecute() {
super.onPreExecute();
dialog = new ProgressDialog(context);
dialog.setCancelable(true);
dialog.setMessage("Loading...");
dialog.setProgress(0);
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
dialog.setMax(maxValue);
dialog.show();
}
#Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
if (this.dialog.isShowing()) {
this.dialog.dismiss();
}
this.callback.onThreadFinish();
}
}
In My Activity:
final String page = htmlPage.substring(start, end).trim();
//Create new instance of the AsyncTask..
new UIThreadProgress(this, new UIThreadCallback() {
#Override
public void run() {
row_id = makeTableRow(row_id, layout, params, matcher); //ADD a row to the table layout.
}
#Override
public void onThreadFinish() {
System.out.println("FINISHED!!");
}
#Override
public boolean condition() {
return matcher.find();
}
}).execute();
So the above creates an async task to run to update a table layout activity while showing the progress bar that displays how much work has been done..
However, I get an error saying that only the thread that started the activity can update its views. I tried changing my Async Task's run to the following:
MainActivity.this.runOnUiThread(new Runnable() {
#Override public void run() {
row_id = makeTableRow(row_id, layout, params, matcher); //ADD a row to the table layout.
}
}
But this gives me synchronization errors.. Any ideas how I can display progress and at the same time update my table in the background?
Currently my UI looks like:
Whatever update that you are doing in the UI do it in progress update, use Global Variables to pass values or use Getter Setter.
Here is a simple example, from one of my current project.
It changes the width of the LinearLayout, which acts as progress bar and also updates the textview with X%. Am updating by calling onProgressUpdate
public class Updater extends AsyncTask<Void, Void, Void> {
#Override
protected void onPreExecute() {
super.onPreExecute();
width = getWindowManager().getDefaultDisplay().getWidth();
Log.wtf(tag, "width" + width);
}
#Override
protected Void doInBackground(Void... params) {
while (updated < sleep) {
try {
Thread.sleep(updateEveryXmillSec);
updated = updated + updateEveryXmillSec;
publishProgress();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}
#Override
protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
mTextView.setText((int) (100 * updated / sleep) + " %");
xwidth = (width * ((int) (100 * updated / sleep)) / 100);
mLayout.setLayoutParams(new RelativeLayout.LayoutParams(xwidth,
height));
}
#Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
startActivity(new Intent(getApplicationContext(), Main.class));
finish();
}
}
Call new Updater().execute(); to trigger the action.
You should split your UI data from the Row Data. Make a RowObject which contains the data to display in the table:
class RowData {
String program;
String course;
String bookName;
// get/set etc
}
You can fill this object in the UIThreadProgress class run method and push it to a synced list.
In onProcessUpdate() you can than build the View Object based on the synced list and add it to the View Hierachie. You are on the UI thread now, and adding should be possible.
You have to care about a synced list during this. Because the Background Thread and the UI Thread will adding and removing objects at the same time. a synchronized will help here. Depending on the speed of your algorithm to calculate the needed data, a faster approach than the synced list is better. But the Idea is always the same. You have to split your data and the View Operations.
I am trying update text views while this loop is processing. Here is my code that I am using for this activity.
public class StartDayActivity extends Activity {
Data data_;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.startdayscreen);
Thread seperationTimer = new Thread(){
public void run(){
int seperationTimer = 5000;
int weatherAffect = 0;
int randomEventAffect = 0;
int locationTotalAvailable = 10;
//int servingTime = 3000;
int totalAvailableCustomers = (weatherAffect + randomEventAffect + locationTotalAvailable);
int dayTimer = (totalAvailableCustomers * seperationTimer);
int seperationTimerDay = 0;
int lemonadeSold = 11;
int totalDrinksSold = 0;
int pitcherSize = 12;
int totalLemonsUsed= 0;
int totalIceUsed =0;
int totalSugarUsed = 0;
int totalCupsUsed = 0;
double drinkPrice = 0.80;
try{
while(seperationTimerDay < dayTimer){
sleep(seperationTimer);
seperationTimerDay = seperationTimerDay + seperationTimer;
lemonadeSold = lemonadeSold + 1;
totalDrinksSold += 1;
data_.cups_ -= 1;
totalIceUsed += 2;
if(lemonadeSold == pitcherSize){
lemonadeSold = 0;
data_.lemons_ -= 4;
data_.sugar_ -= 2;
totalSugarUsed +=2;
totalLemonsUsed +=4;
}
if(data_.lemons_ == 0 || data_.ice_ == 0 || data_.sugar_ ==0 || data_.cups_ == 0){
break;
}
updateView();
Log.d("TAG", "for each : " + data_.ice_);
//calcMarketing();
}
data_.day_gross_profit_ = (totalDrinksSold * drinkPrice);
data_.cash_ += (totalDrinksSold * drinkPrice);
Intent i = new Intent("com.game.lemonade.PLAYSCREEN");
startActivity(i);
}
catch(InterruptedException e)
{
e.printStackTrace();
}
finally{
finish();
}
}
};
seperationTimer.start();
}
#Override
public void onStart(){
super.onStart();
data_ = getData();
}
#Override
public void onResume(){
super.onResume();
data_ = getData();
//refreshDisplay();
}
#Override
public void onPause(){
super.onPause();
saveData();
//refreshDisplay();
}
#Override
public void onConfigurationChanged(Configuration newConfig){
super.onConfigurationChanged(newConfig);
//refreshDisplay();
}
private Data getData() {
GameData player_data = new GameData(this);
player_data.open();
Data game_state = new Data(player_data.getGameString());
player_data.close();
return game_state;
}
private void saveData() {
GameData player_data = new GameData(this);
player_data.open();
player_data.setGameString(data_.SerializeGame());
player_data.close();
}
private void updateView(){
((TextView)findViewById(R.id.lemonsLeftText2)).setText(
(data_.lemons_) );
}
}
Is there a better way to try to do this? And how would I want to do that? I get an error when I do the updateView() to try to update textview.
Error message already contains answer to your question. You can not update UI from different thread - use runOnUIThread() to do this. And for battery sake - get reference to textView in onCreate() and reuse it.
You cant access the UI using threads directly.Either use an AsyncTask or Handler for your longer thread operations which effect the UI.
Check these;
http://developer.android.com/resources/articles/painless-threading.html
http://developer.android.com/reference/android/os/Handler.html
You are not allowed to change the appearance of your Views from any thread that is not the main thread.
#Serdar has some great suggestions for how to handle that.
Also you should avoid calling findViewById() each time you need to call setText(). findViewById() is a fairly expensive call in terms of system resources. The fewer calls like that you make the better your app will run.
I suggest you make yourself a TextView reference and make your findViewById() call inside onCreate() and set your reference to what it returns to you. Then in your updateView() method call something like yourTxt.setText("");
Although the other answers have covered the answer to your question, I'll point you at this section in the documentation.
Threads
In particular the last part.
Additionally, the Andoid UI toolkit is not thread-safe. So, you must not manipulate your UI from a worker thread—you must do all manipulation to your user interface from the UI thread. Thus, there are simply two rules to Android's single thread model:
Do not block the UI thread
Do not access the Android UI toolkit from outside the UI thread
Two very simple rules indeed but if you stick to them you'll avoid a lot of problems.
You should use Handler class:
Where you call:
updateView();
replace with:
myHandler.sendEmptyMessage(0);
In the handler:
private Handler myHandler=new Handler(){
#Override
public void handleMessage(Message msg) {
if(msg.what==0){
updateView();
}
};
};
I currently have an Android activity which manages some locally-stored RSS feeds. In this activity, these feeds are updated in their own thread via a private class. I'm also trying to include an "updating" icon that rotates with a RotateAnimation while this thread is running.
The animation works by itself, but doesn't work while the thread is running despite the log entries stating the code is being executed. I suspect this is due to the thread not being entirely safe, and taking up most of the CPU time. However I'd just like to know if there's a better way of achieving this.
The function updateAllFeeds() is called from a button press. Here's the relevant code:
/**
* Gets the animation properties for the rotation
*/
protected RotateAnimation getRotateAnimation() {
// Now animate it
Log.d("RSS Alarm", "Performing animation");
RotateAnimation anim = new RotateAnimation(359f, 0f, 16f, 21f);
anim.setInterpolator(new LinearInterpolator());
anim.setRepeatCount(Animation.INFINITE);
anim.setDuration(700);
return anim;
}
/**
* Animates the refresh icon with a rotate
*/
public void setUpdating() {
btnRefreshAll.startAnimation(getRotateAnimation());
}
/**
* Reverts the refresh icon back to a still image
*/
public void stopUpdating() {
Log.d("RSS Alarm", "Stopping animation");
btnRefreshAll.setAnimation(null);
refreshList();
}
/**
* Updates all RSS feeds in the list
*/
protected void updateAllFeeds() {
setUpdating();
Updater updater = new Updater(channels);
updater.run();
}
/**
* Class to update RSS feeds in a new thread
* #author Michael
*
*/
private class Updater implements Runnable {
// Mode flags
public static final int MODE_ONE = 0;
public static final int MODE_ALL = 1;
// Class vars
Channel channel;
ArrayList<Channel> channelList;
int mode;
/**
* Constructor for singular update
* #param channel
*/
public Updater(Channel channel) {
this.mode = MODE_ONE;
this.channel = channel;
}
/**
* Constructor for updating multiple feeds at once
* #param channelList The list of channels to be updated
*/
public Updater(ArrayList<Channel> channelList) {
this.mode = MODE_ALL;
this.channelList = channelList;
}
/**
* Performs all the good stuff
*/
public void run() {
// Flag for writing problems
boolean write_error = false;
// Check if we have a singular or list
if(this.mode == MODE_ONE) {
// Updating one feed only
int updateStatus = channel.update(getApplicationContext());
// Check for error
if(updateStatus == 2) {
// Error - show dialog
write_error = true;
}
}
else {
// Iterate through all feeds
for(int i = 0; i < this.channelList.size(); i++) {
// Update this item
int updateStatus = channelList.get(i).update(getApplicationContext());
if(updateStatus == 2) {
// Error - show dialog
write_error = true;
}
}
}
// If we have an error, show the dialog
if(write_error) {
runOnUiThread(new Runnable(){
public void run() {
showDialog(ERR_SD_READ_ONLY);
}
});
}
// End updater
stopUpdating();
} // End run()
} // End class Updater
(I know the updateStatus == 2 bit is bad practice, that's one of the next things I plan to tidy up).
Any help is greatly appreciated, many thanks in advance.
Run the updater runnable in a separate thread. Do the following changes.
protected void updateAllFeeds() {
setUpdating();
new Thread( new Updater(channels)).start();
}
Call stopUpdating() in runOnUiThread block.
private class Updater implements Runnable {
.........
.........
.........
public void run() {
.........
.........
// End updater
runOnUiThread(new Runnable(){
public void run() {
stopUpdating();
}
});
} // End run()
} // End class Updater
Move anything that affects the UI off into its own Runnable and then post it with your button
btnRefreshAll.post(new StopUpdating());
I managed to get this working last night using Android's AsyncTask class. It was surprisingly easy to implement, though the downside was I had to write one class for updating an individual feed, and another for updating all feeds. Here's the code for updating all feeds at once:
private class MassUpdater extends AsyncTask<ArrayList<Channel>, Void, Void> {
#Override
protected Void doInBackground(ArrayList<Channel>... channels) {
ArrayList<Channel> channelList = channels[0];
// Flag for writing problems
boolean write_error = false;
// Iterate through all feeds
for(int i = 0; i < channelList.size(); i++) {
// Update this item
int updateStatus = channelList.get(i).update(getApplicationContext());
if(updateStatus == FileHandler.STATUS_WRITE_ERROR) {
// Error - show dialog
write_error = true;
}
}
// If we have an error, show the dialog
if(write_error) {
runOnUiThread(new Runnable(){
public void run() {
showDialog(ERR_SD_READ_ONLY);
}
});
}
return null;
}
protected void onPreExecute() {
btnRefreshAll.setAnimation(getRotateAnimation());
btnRefreshAll.invalidate();
btnRefreshAll.getAnimation().startNow();
}
protected void onPostExecute(Void hello) {
btnRefreshAll.setAnimation(null);
refreshList();
}
}
Thanks for your answers guys. userSeven7s' reply also makes a lot of sense so I may use that as a backup should I run into any problems with AsyncTask.
I have a custom DownloadFiles class which extends AsyncTask. The app launches itself and starts an AsyncTask in background for a default URL. On the UI, I have a number of buttons - each of them onClick event should cancel the current DownloadFiles instance and create new one with a different URL.
private OnClickListener categoryClick = new OnClickListener(){
public void onClick(View view) {
dft.cancel(true);
int index = catigoriesHolder.indexOfChild(view);
activeCategory.setText(categories[index]);
dft = new DownloadFilesTask();
dft.execute(rssFeedURL[index]);
dft.execute(url);
}
};
dft is my DownloadFiles object, witch I need to cancel. And here is my onCancelled method:
protected void onCancelled() {
super.onCancelled();
}
I tried also to check inside the click-event if the dft is cancelled and then to create a new instance of my custom downloadable class. How could be this done and why when I trying this:
private OnClickListener categoryClick = new OnClickListener(){
public void onClick(View view) {
if (dft.isCancelled()) {
int index = catigoriesHolder.indexOfChild(view);
activeCategory.setText(categories[index]);
dft = new DownloadFilesTask();
dft.execute(rssFeedURL[index]);
} else {
dft.cancel(true);
}
}
};
it does not work, even on the second click?
I tried this after you advised me to check for isCancelled() inside the doInBackground(...)
private OnClickListener categoryClick = new OnClickListener(){
public void onClick(View view) {
int index = catigoriesHolder.indexOfChild(view);
activeCategory.setText(categories[index]);
if (dft.isCancelled()) {
dft.cancel(false);
dft = new DownloadFilesTask();
dft.execute(rssFeedURL[index]);
} else {
dft.cancel(true);
}
}
};
But what happens, in this case:
- the default URL works fine, but when I click on a button, there is no action.
protected List<RSSItem> doInBackground(String... urls) {
parse(urls[0]);
if (feed == null) { return null; }
rssList = feed.getAllItems();
Iterator<RSSItem> iterator = rssList.iterator();
while(iterator.hasNext()) {
if (isCancelled()) return null;
RSSItem rss = iterator.next();
publishProgress(rss);
}
return rssList;
}
After I checked the status of the current AsyncTask, it works:
private OnClickListener categoryClick = new OnClickListener(){
public void onClick(View view) {
String status = dft.getStatus().name();
if (status.equals("FINISHED") || dft.isCancelled()) {
dft = new DownloadFilesTask();
int index = catigoriesHolder.indexOfChild(view);
activeCategory.setText(categories[index]);
dft.execute(rssFeedURL[index]);
} else {
dft.cancel(true);
}
}
};
But still doesn't fit my expectations. Currently you should click twice to get new instance of DownloadFiles class. Could to transform it to a single click?
The .cancel(true) method only changes the internal canceled variable in the task. You have to call isCancelled() inside doInBackground() periodically and return when it evaluates true.
Also, please specify what is not working in each case.