Logo Icon Logo
A Crowd-sourced Cookbook on Writing Great Android® Apps
GitHub logo Twitter logo OReilly Book Cover Art
HomeF.A.Q.
Community
Writing Recipes
Login
Using AsyncTask to do background processing
Contributed by Johan Pelgrim 2011-07-21 02:39:21 (updated 2011-10-02 08:15:56)
In Published Edition? Yes
0
Votes
Problem

You have to do some heavy processing, or load resources from the network and want to show the progress and results in the UI.

Solution

Use AsyncTask and ProgressDialog to achieve this

Discussion

Introduction

As we can read in the Processes and Threads section of the Android Dev Guide we know blocking the UI thread or or accessing the Android UI toolkit from outside the UI thread are not done. Not... Ever... NEVER! Bad things will happen to you when you do... (you cannot repeat this often enough).

There are several methods to run processes in the background and updating the UI inside the UI thread (a.k.a. main thread) but using the AsyncTask class is very convenient and should be in every Android Developer's toolkit.

It boils down to creating a class which extends AsyncTask. AsyncTask itself is abstract and has one abstract method, Result doInBackground(Params... params);. The AsyncTask simply creates a callable working thread in which your implementation of doInBackground runs. Result and Params are two of the types we need to define in our class definition. The third is the Progress type which we will talk about later.

Let's revisit the Parsing an XML document using an XmlPullParser recipe. The processing bit processes the content of a web page, which is an XML document, and returns the result as a list of Datum objects. Typically something we want to do outside of the UI thread.

Our first implementation will do everything in the background, showing the user a spinner in the title bar and updating the ListView once the processing is done. This is the typical use case, not interfering with the user's task at hand and updating the UI when you have retrieved the result.

The second implementation will use a modal dialog to show the processing progressing in the background. In some cases we want to prevent the user from doing anything else when some processing takes place and this is a good way to do just that.

We will create a UI which contains three Buttons and a Listview. The first button is to kick of our first refresh process. The second for the other refresh process and the third to clear the results in the ListView

<?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">
	<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
		android:orientation="horizontal" android:layout_width="fill_parent"
		android:layout_height="wrap_content">
		<Button android:text="Refresh 1" android:id="@+id/button1"
			android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_weight="1"></Button>
		<Button android:text="Refresh 2" android:id="@+id/button2"
			android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_weight="1"></Button>
		<Button android:text="Clear" android:id="@+id/button3"
			android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_weight="1"></Button>
	</LinearLayout>
	<ListView android:id="@+id/listView1" android:layout_height="fill_parent"
		android:layout_width="fill_parent"></ListView>
</LinearLayout>

We assign these UI elements to various fields in onCreate and add some click listeners.

    ListView mListView;
    Button mClear;
    Button mRefresh1;
    Button mRefresh2;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
		
        mListView = (ListView) findViewById(R.id.listView1);
        mListView.setTextFilterEnabled(true);
        mListView.setOnItemClickListener(this);
        
        mRefresh1 = (Button) findViewById(R.id.button1);
        
        mClear = (Button) findViewById(R.id.button3);
        mClear.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                mListView.setAdapter(null);
            }
        });
        
    }
	
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        Datum datum = (Datum) mListView.getItemAtPosition(position);
        Uri uri = Uri.parse("http://androidcookbook.com/Recipe.seam?recipeId=" + datum.getId());
        Intent intent = new Intent(Intent.ACTION_VIEW, uri);
        this.startActivity(intent);
    }
	

Use Case 1: Processing in the background

First we create an inner class which extends AsyncTask

    protected class LoadRecipesTask1 extends AsyncTask<String, Void, ArrayList<Datum>> {
        
    }

As we can see we must supply three types to the class definition. The first is the type of the parameter which we will provide when starting this background task, in our case a String, containing a URL. The second type is used for progress updates (we will use this later). The third type is the type which is returned by our implementation of the doInBackground method, and typically something you can update a specific UI element with (a ListView in our case).

Let's implement the doInBackground method

        @Override
        protected ArrayList<Datum> doInBackground(String... urls) {
            ArrayList<Datum> datumList = new ArrayList<Datum>();
            try {
                datumList = parse(urls[0]);
            } catch (IOException e) {
                e.printStackTrace();
            } catch (XmlPullParserException e) {
                e.printStackTrace();
            }
            return datumList;
        }

As you can see this is pretty simple. The parse method -- which creates a list of Datum objects -- is described in the Parsing an XML document using an XmlPullParser recipe. The result of the doInBackground method is then passed as an argument to the onPostExecute method in the same (inner) class. In this method we are allowed to update the UI elements in our layout, so we set the adapter of the ListView to show our result.

        @Override
        protected void onPostExecute(ArrayList<Datum> result) {
            mListView.setAdapter(new ArrayAdapter<Datum>(MainActivity.this, R.layout.list_item, result));
        }

Now we have to somehow start this task. We do this in the mRefresh1's onClickListener by calling the execute(Params... params) method of AsyncTask (execute(String... urls) in our case).

        mRefresh1.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                LoadRecipesTask1 mLoadRecipesTask = new LoadRecipesTask1();
                mLoadRecipesTask.execute("http://androidcookbook.com/seam/resource/rest/recipe/list");
            }
        });

Now, when you start the app it indeed retrieves the recipes and fills the ListView but the user has no idea that something is happening in the background. We can set the progress bar indeterminate window feature in this case, which displays a small progress animation in the top-right of our app title bar.

To do this we request this feature by calling the following method in onCreate: requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);

Then we can start the progress animation by calling the setProgressBarIndeterminateVisibility(Boolean visibility) method from within a new method in our inner class, the onPreExecute method.

        protected void onPreExecute() {
            MainActivity.this.setProgressBarIndeterminateVisibility(true); 
        }

We stop the spinning progress bar in our window title by calling the same method from within our onPostExecute method, which will become:

        protected void onPostExecute(ArrayList<Datum> result) {
            mListView.setAdapter(new ArrayAdapter<Datum>(MainActivity.this, R.layout.list_item, result));
            MainActivity.this.setProgressBarIndeterminateVisibility(false); 
        }

We're done! Take your app for a spin (pun intended).

A nifty feature for creating a better user experience!

Use Case 2: Processing in the foreground

In our second example we show a modal dialog to the user which displays the progress of loading the recipes in the background. Such a dialog is called a ProgressDialog. First we add it as a field to our activity.

    ProgressDialog mProgressDialog;

Then we add the onCreateDialog method to be able to answer showDialog calls and create our dialog.

    protected Dialog onCreateDialog(int id) {
        switch (id) {
        case DIALOG_KEY:                                                               // 1
            mProgressDialog = new ProgressDialog(this);
            mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);         // 2
            mProgressDialog.setMessage("Retrieving recipes...");                       // 3
            mProgressDialog.setCancelable(false);                                      // 4
            return mProgressDialog;
        }
        return null;
    }
  1. We should handle the request and creation of all dialogues here. The DIALOG_KEY is an int constant with an arbitrary value (we used 0) to identify this dialog.
  2. We set the progress style to STYLE_HORIZONTAL, which shows a horizontal progress bar. The default is STYLE_SPINNER
  3. We set our custom message, which is displayed above the progress bar
  4. By calling setCancable with argument false we simply disable the back-button, making this dialog modal

Then our new implementation of AsyncTask

    protected class LoadRecipesTask2 extends AsyncTask<String, Integer, ArrayList<Datum>> {
        
        @Override
        protected void onPreExecute() {
            mProgressDialog.show();                                                          // 1
        }
        
        @Override
        protected ArrayList<Datum> doInBackground(String... urls) {
            ArrayList<Datum> datumList = new ArrayList<Datum>();
            for (int i = 0; i < urls.length; i++) {                                          // 2
                try {
                    datumList = parse(urls[i]);
                    publishProgress((int) (((i+1) / (float) urls.length) * 100));            // 3
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (XmlPullParserException e) {
                    e.printStackTrace();
                }
            }
            return datumList;
        }
        
        @Override
        protected void onProgressUpdate(Integer... values) {                                 // 4
            mProgressDialog.setProgress(values[0]);                                          // 5
        }
        
        @Override
        protected void onPostExecute(ArrayList<Datum> result) {
            mListView.setAdapter(new ArrayAdapter<Datum>(MainActivity.this, R.layout.list_item, result));
            mProgressDialog.dismiss();                                                       // 6
        }
    }

We see a couple of new things here.

1. Before we start our background process we show the modal dialog. 2. In our background process we loop through all the urls, expecting to receive more than one. This will give us a good indication on our progress. 3. We can update the progress by calling publishProgress. Notice that the argument is of type int, which will be autoboxed to the second type defined in our class definition, Integer. 4. The call to publishProgress will result in a call to onProgresUpdate which again has arguments of type Integer. You could of course use String or something else as the argument type by simply changing the second type in the inner class defition to String and of course in the call to publishProgress. 5. We use the first Integer to set the new progress value in our ProgressDialog 6. Finally we dismiss the dialog, which removes it

Now we can all bind this together by implementing our onClickListener for our second refresh button.

        mRefresh2.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                LoadRecipesTask2 mLoadRecipesTask = new LoadRecipesTask2();
                String url = "http://androidcookbook.com/seam/resource/rest/recipe/list";
                showDialog(DIALOG_KEY);                                                     // 1
                mLoadRecipesTask.execute(url, url, url, url, url);                          // 2
            }
        });
  1. We show the dialog by calling showDialog with the DIALOG_KEY argument, which will trigger our previosly defined onCreateDialog method.
  2. We execute our new task with 5 URLs, simply to show a little bit of progress.

It will look something like this.

Conclusion

Implementing background tasks with AsyncTask is very simple and should be done for all long running processes which also need to update your user interface.

Comments (0)
Leave a comment
Edit History (8)
There are no (moderator-approved) comments on this recipe yet.