Showing Progress with Android

Passing data with Handlers, Messages and the ProgressDialog

Threading

Threading is no small topic in programming circles.

Some folks love them. Some hate them.

Whether they are good or evil is a debate for the pious among us — I use them when they fit and you might want to do the same.

In this article we’re going to have a look at a basic building block of Android applications — performing an operation in a secondary thread while keeping the primary GUI thread accessible and the user up to date at all times.

Moonlighting

A common requirement with a mobile application is to perform some marginally long running task, such as transferring data to or from a server on the Internet. If this can be done in the “background” all the better. If the user isn’t preoccupied with the data refresh operation, they can enjoy other aspects of their device without concern.

However, sometimes the ideal thing to do as an application programmer is to show the user the progress of how this operation is proceeding. This can be appropriate for example when submitting a search request or executing an online commerce transaction. Showing up to date progress can greatly pacify even the most impatient of users. On the other hand, a quick transaction with no user feedback is likely to leave uncertainty and doubt in the minds of your user.

It is apparent that we need to keep the user informed.

So, we’ve got two objectives:

  • Perform a task on a background thread so the GUI is free for user interaction, including an optional “cancel” button.
  • Update the GUI in a tasteful manner to keep the user up to speed on our progress through out the operation.

Unfortunately we cannot simply update a user interface element from a secondary thread. The primary GUI thread really likes to make those updates itself and will do nasty things to your application if you try to bend the rules. So, how do we go about this? Let’s find out.

An example

For the purposes of illustrating this task, we’re going to create a simple application which demonstrates the creation of a secondary thread which subsequently performs a long-running transaction. In our case, it is just going to count up to a gagillion or some other large number — like the current federal deficit — it doesn’t matter what it is doing so long as it takes a few moments to complete.

The application has a super simple UI to it: just a label and a Button. Here is the UI layout:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="@string/hello"
    />
   <Button android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:text="Run Test"
   android:id="@+id/TestThreads"
   />
</LinearLayout>

And here is how it looks.

Bland test application
Bland test application

I warned you, pretty boring, but it meets our needs.

Once the button is selected, the application simply starts counting in a new java.lang.Thread. Every 100,000 iterations, we have the application come back and let us know that we’re still here by updating the ProgressDialog with a call to setMessage().

Showing progress updates
Showing progress updates

Yes, that’s the entire application — not very many attractions, but there is quite a bit to discuss to make this happen. Lets look the code to implement this multi-yhreaded application that can safely update the user interface

The code

The entire application consists of a single Java source file, namely Threading.java.

package com.msi.linuxmag;

import android.app.Activity;
import android.os.Bundle;
import android.os.Message;
import android.os.Handler;
import android.app.ProgressDialog;
import android.widget.Button;
import android.view.View;

public class Threading extends Activity {
	Handler myHandler;
	ProgressDialog myProgress;
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        final Button btnTest = (Button) findViewById(R.id.TestThreads);
        btnTest.setOnClickListener(new Button.OnClickListener()
        {
        	public void onClick(View v)
        	{
        		 myProgress = ProgressDialog.show(Threading.this, "Linux Magazine Demo", "Counting down ...", true,false);
                 myHandler = new Handler() {

                     @Override
                     public void handleMessage(Message msg) {
                         // process incoming messages here
                         switch (msg.what) {
                             case 0:
                                 // update progress bar
                                 myProgress.setMessage("" + (String) msg.obj);
                                 break;
                             case 1:
                            	 myProgress.cancel();
                                 finish();
                                 break;
                             case 2:
                            	 myProgress.cancel();
                             	break;
                         }
                         super.handleMessage(msg);
                     }

                 };

                 Thread workthread = new Thread(new SidelineThread());

                 workthread.start();
        	}
        }

        );
    }

    public class SidelineThread implements Runnable
    {
    	SidelineThread()
    	{
    		// constructor.  do nothing
    	}

    	public void run()
    	{
    		Message msg;
    		long i = 0;
    		while (i < 10000000)
    		{
    			if (i % 100000 == 0)
    			{
    				msg = new Message();
    				msg.what = 0;
    				msg.obj = "Processed " + i + " so far.";
    				myHandler.sendMessage(msg);
    			}
    			i++;
    		}
    		msg = new Message();
    		msg.what = 1;
    		myHandler.sendMessage(msg);
    	}
    }
}

Here are the high level items to note in the application:

  • There are two class-level variables, one for the Handler and another for the ProgressDialog. Both of these are initialized in the OnClick() method of the Button’s listener.
  • The onCreate method of the Activity simply inflates the user inteface layout and then wires up the lone Button widget with an OnClickListener.
  • The OnClick() method takes care of initiating our test. Notice that this method creates a ProgressDialog, a Handler and then starts a Thread.
  • The Handler works by creating in essence a “callback” mechanism for the application to employ. Code running in the newly created Thread can “callback” to the primary application. Unlike traditional callback functions, this technique actually passes data in the form of an android.os.Message object, rather than “calling a function”.
  • The Handler’s code is setup to expect three different values for the what member: 0,1, and 2. These are entirely arbitrary values.
  • A value of 0 instructs the Handler to display a message on the ProgressDialog. The message is actually contained as a String and stored as the obj field. A value of 1 tells the Handler that the Thread is complete and the Activity can terminate with a call to finish(). In the case where the Handler receives a 2, it is used to indicate an error condition and we don’t want the Activity to disappear, lest we lose the data.
  • The SidelineThread class demonstrates a simple long-running class. Whenever this class wants to “communicate” with the user, it must do so through by way of the primary UI thread. Fortunately all that is required to accomplish this feat is to pass a Message Object to its Handler. These Message instances are objects that are garbage collected so it is advisable to create a new Message for each and every communication sent to the Handler.

That is about there is to it — for now. In the future we’ll have a look at the AsyncTask class, which presents a slightly different approach to the same challenge.

Fatal error: Call to undefined function aa_author_bios() in /opt/apache/dms/b2b/linux-mag.com/site/www/htdocs/wp-content/themes/linuxmag/single.php on line 62