Providing feedback to the user

Having started what we know to be a potentially long-running task, we probably want to let the user know that something is happening. There are a lot of ways of doing this, but a common approach is to present a dialog displaying a relevant message.

A good place to present our dialog is from the onPreExecute method of AsyncTask, which executes on the main thread. Hence, it is allowed to interact with the user interface.

The modified PrimesTask will need a reference to a Context, so that it can prepare a ProgressDialog, which it will show and dismiss in onPreExecute and onPostExecute respectively. As doInBackground has not changed, it is not shown in the following code, for brevity:

public class PrimesTask extends AsyncTask<Integer, Void, BigInteger>{
  private Context ctx;
  private ProgressDialog progress;
  private TextView resultView;

  public PrimesTask(Context ctx, TextView resultView) {
    this.ctx = ctx;
      this.resultView = resultView;
  }

  @Override
  protected void onPreExecute() {
    progress = new ProgressDialog(ctx);
    progress.setTitle(R.string.calculating);
    progress.setCancelable(false);
    progress.show();
  }

  // … doInBackground elided for brevity …

  @Override
  protected void onPostExecute(BigInteger result) {
    resultView.setText(result.toString());
    progress.dismiss();
  }
}

All that remains is to pass a Context to the constructor of our modified PrimesTask. As Activity is a subclass of Context, we can simply pass a reference to the host Activity:

goButton.setOnClickListener(new View.OnClickListener() {       
  public void onClick(View view) {
    new PrimesTask(
      PrimesActivity.this, resultView).execute(500);
  }
});

Providing progress updates

Knowing that something is happening is a great relief to our users, but they might be getting impatient and wondering how much longer they need to wait. Let's show them how we're getting on by adding a progress bar to our dialog.

Remember that we aren't allowed to update the user interface directly from doInBackground, because we aren't on the main thread. How, then, can we tell the main thread to make these updates for us?

AsyncTask comes with a handy callback method for this, whose signature we saw at the beginning of the chapter:

protected void onProgressUpdate(Progress… values)

We can override onProgressUpdate to update the user interface from the main thread, but when does it get called and where does it get its Progress… values from? The glue between doInBackground and onProgressUpdate is another of AsyncTask's methods:

protected final void publishProgress(Progress... values)

To update the user interface with our progress, we simply publish progress updates from the background thread by invoking publishProgress from within doInBackground. Each time we call publishProgress, the main thread will be scheduled to invoke onProgressUpdate for us with these progress values.

The modifications to our running example to show a progress bar are quite simple. First, we must change the class declaration to include a Progress type. We'll be setting progress values in the range 0 to 100, so we'll use Integer:

public class PrimesTask
extends AsyncTask<Integer, Integer, BigInteger> {

Next, we need to set the style and the bounds of the progress bar. We can do that with the following additions to onPreExecute:

progress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progress.setProgress(0);
progress.setMax(100);

We also need to implement the onProgressUpdate callback to update the progress bar from the main thread:

@Override
protected void onProgressUpdate(Integer... values) {
  progress.setProgress(values[0]);
}

The final modification is to calculate the progress at each iteration of the for loop, and invoke publishProgress so that the main thread knows to call back onProgressUpdate:

protected BigInteger doInBackground(Integer... params) {
  int primeToFind = params[0];
  BigInteger prime = new BigInteger("2");
  int progress = 0;
  for (int i=0; i<primeToFind; i++) {
    prime = prime.nextProbablePrime();
    int percent = (int)((i * 100f)/primeToFind);
    if (percent > progress) {
      publishProgress(percent);
      progress = percent;
    }
  }
  return prime;
}

It is important to understand that invoking publishProgress does not directly invoke the main thread, but adds a task to the main thread's queue, which will be processed at some time in the near future by the main thread.

Notice that we're being careful to publish progress only when the percentage actually changes, avoiding any unnecessary overhead.

The delay between publishing the progress and seeing the user interface update will be extremely short, and the progress bar will update smoothly, provided we are careful to follow the golden rule of not blocking the main thread from any of our code.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.12.147.77