Monday, March 05, 2012

Android ProgressDialog and Threads

Many tasks require an application to get or post data from/to some web service. Since internet communication can be quick or quite lengthy, it's necessary to notify the user that some work is occurring. Android's ProgressDialog gives you two options: a dialog with a bar (like a copy dialog on Windows) or a spinner dialog (typical for Ajax web apps).

Unfortunately, using the progress dialog can be painful. Frustratingly, many tutorials show you how to instantiate a ProgressDialog but not how to use it properly, failing to mention that if you make a blocking call on the UI thread (like an HTTP request), the ProgressDialog will never actually appear. After 2 seconds, the Android OS will think your app has frozen and kill it. Great.

Many forum posts suggest using Android's AsyncTask to execute the "work" in the background (AsyncTask is supposed to be easier to use than creating a new thread), but I've found AsyncTask to be more headache than help. Just create a thread. It's not hard.

FYI, ProgressDialog requires a context (the calling activity), a message to display, and a handler to do some work. Your handler can only receive ONE variable, so I tend to use a HashMap so I can pass multiple bits of data.

I've come up with a pattern that I've reused and works well. Here we go!

  1. Create a thread class.

  2. public class WorkerThread extends Thread {
    private ProgressDialog dialog;
    private Handler handler;
    private HashMap messageData = new HashMap();

    // .. put your constructor etc here ...
    public WorkerThread(ProgressDialog dialog, Handler handler) {
    this.dialog = dialog;
    this.handler = handler;
    }

    public void run() {
    // Var to keep track of whether the work succeeded or not
    Boolean status = true;
    // ... do some work here ...

    // If an error occurred...
    if(error) {
    status = false;
    messageData.put("message", "Error message goes here");
    }
    messageData.put("status", status);

    // Send a message back to calling activity
    handler.obtainMessage(0x2a, messageData).sendToTarget();

    // Dismiss dialog
    if (dialog != null && dialog.isShowing())
    dialog.dismiss();
    }

    // Clean up if the thread is cancelled
    public void cancel() {
    messageData.put("status", false);
    handler.obtainMessage(0x2a, messageData).sendToTarget();
    if (dialog != null && dialog.isShowing())
    dialog.dismiss();
    }
    }
  3. Add a thread and a null handler to your activity.
  4. WorkerThread WorkerThreadInstance = null;
    Handler handler = null;
  5. Create the ProgressDialog in your Activity's onCreate

  6. ProgressDialog workDialog = ProgressDialog.show(this, "", "Working...", true);
  7. Create a handler.

  8.   // Handle response from the worker thread
    handler = new Handler() {
    @SuppressWarnings("unchecked")
    @Override
    public void handleMessage(Message msg) {
    super.handleMessage(msg);
    HashMap data = (HashMap) msg.obj;
    Boolean status = (Boolean) data.get("status");

    if (status==true) {// if successful
    Toast.makeText(getApplicationContext(), "Work was successful",
    2000).show();
    // Process return data here
    // Uncomment the next line if your activity should end once processing is done
    //finish();
    } else {
    if(data.get("message")!=null) {
    Toast.makeText(getApplicationContext(), "Work failed!" + data.get("message") ,
    3000).show();
    }
    // Uncomment the next line if your activity should end after an error
    //finish();
    }
    }
    };
  9. Instantiate your worker thread

  10.   // Create an instance of the worker thread
    WorkerThreadInstance = new WorkerThread(workDialog, handler);
    WorkerThreadInstance.start();
  11. Implement onPause for your activity to stop the worker thread if the activity is paused(and possibly onResume-- though be aware that onResume will be called before onCreate)

  12.  @Override
    public void onPause() {
    super.onPause();

    if (WorkerThreadInstance != null) {
    WorkerThreadInstance.cancel();

    // Mark thread for deletion by GC or there will be a memory leak
    WorkerThreadInstance = null;
    }
    }

Labels

Blog Archive

Contributors