Apps

Android's indeterminate ProgressDialog tutorial

Delve into how to use the Android ProgressDialog class to create a better user experience.

In my dream world, a user would never have to wait on an application to fetch data from a server. Sadly, while the digital pipelines that feed the latest and greatest mobile apps continue to widen, we haven't quite reached the point of instantaneous data.

When it comes to writing mobile applications, there are a number of tricks-of-the-trade to shield the user from much of the tedious crunching that goes on in the background. There are still times when the better user experience (UX) is to display a "please wait" dialog, rather than leave the user scratching his head and wondering what is going on.

Fortunately, the Android framework has a number of built-in classes to handle these situations. One such class is the ProgressDialog. The example that follows displays a dialog and spinner while some background work goes on. As is standard practice on the Android platform, any background work needs to be moved off of the user interface (UI) thread. This is generally accomplished with the AsyncTask class.

Feel free to follow along with the step-by-step tutorial below, or, download and import the entire project into Eclipse.

1. Create a new Android project in Eclipse. Target Android 1.6 or greater. Be sure to rename the startup activity to Main.java and the associated layout to main.xml.

2. In the /res folder, create a linear layout that holds a single text field and button.

main.xml
<LinearLayout xmlns:android=<em>"http://schemas.android.com/apk/res/android"</em>
    android:layout_width=<em>"fill_parent"</em>
    android:layout_height=<em>"fill_parent"</em>
    android:orientation=<em>"vertical"</em>
    android:gravity=<em>"center"</em>>
    <TextView
        android:layout_width=<em>"wrap_content"</em>
        android:layout_height=<em>"wrap_content"</em>
        android:textSize=<em>"20sp"</em>
        android:textColor=<em>"#0000ff"</em>
        android:paddingBottom=<em>"20dip"</em>
<em>        </em><span style="text-decoration: underline;">android:text=<em>"Progress Dialog Demo"</em></span> />
    <Button
        android:layout_width=<em>"wrap_content"</em>
        android:layout_height=<em>"wrap_content"</em>
        <span style="text-decoration: underline;">android:text=<em>"Do Background Processing"</em></span>
        android:id=<em>"@+id/the_button"</em> />
</LinearLayout>

3. In the /src folder, modify Main.java. In the on create override, assign the activity context and wire up our button listener.

package com.authorwjf.progressdialog;

import com.wjf.progressdialog.R;

import android.os.AsyncTask;
import android.os.Bundle;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class Main extends Activity implements OnClickListener{
	
	private Context context;
	private ProgressDialog pd;
	private Button b;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        context = this;
        b = (Button) findViewById(R.id.the_button);
        b.setOnClickListener(this);
    }
    
    @Override 
    protected void onDestroy() {
    	if (pd!=null) {
			pd.dismiss();
			b.setEnabled(true);
		}
    	super.onDestroy();
    }
    
}

4. The on click view handler is responsible for disabling the button and then spawning a new AsyncTask that will handle the background work. In the pre and post execute methods, we worry about the ProgressDialog and re-enabling the button.

@Override
public void onClick(View v) {
	v.setEnabled(false);
	AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
		
		@Override
		protected void onPreExecute() {
			pd = new ProgressDialog(context);
			pd.setTitle("Processing...");
			pd.setMessage("Please wait.");
			pd.setCancelable(false);
			pd.setIndeterminate(true);
			pd.show();
		}
			
		@Override
		protected Void doInBackground(Void... arg0) {
			try {
				//Do something...
				Thread.sleep(5000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			return null;
		}
		
		@Override
		protected void onPostExecute(Void result) {
			if (pd!=null) {
				pd.dismiss();
				b.setEnabled(true);
			}
		}
			
	};
	task.execute((Void[])null);
}

Now our demo is ready to run. Before loading it to a device, take a closer look at the onPreExecute method of the AsyncTask. Notice how we set up a number of properties on the ProgressDialog? The title and message properties are fairly self-explanatory, but you may be curious about the cancelable and indeterminate properties. The first property specifies whether the user can cancel the dialog by tapping outside of it. The second property tells the dialog whether to display a spinner. If false, the ProgressDialog will contain a real progress bar, and you will need to periodically update the UI with the current completion percent of your background task.

Indeterminate progress dialogs should never be your first UX choice. But don't be afraid to use them when the alternative is to leave the user wondering if your application has suffered cardiac arrest.

Note: This tutorial is focused on the progress dialog and as such ignores what happens to the long-running I/O itself when an orientation takes place. I’ve written another tutorial on long-running I/O that delves into passing asynchronous tasks between life cycle events

About

William J Francis began programming computers at age eleven. Specializing in embedded and mobile platforms, he has more than 20 years of professional software engineering under his belt, including a four year stint in the US Army's Military Intellige...

1 comments
authorwjf
authorwjf

Thanks to Robert Zollo for pointing out a bug in my example.  It's since been updated and a new zip file was uploaded.

Editor's Picks