Asynchronous Image Loader in Android ListView

1. Introduction

As mobile devices are limited with memory, we must follow certain best practices to provide best performance smooth user experience. Among set of best practices, the one holds most priority is to take the long running heavy operations off the main thread.

Any long running tasks or heavy operations are usually performed in a different thread, to make sure your main thread does the minimum amount of work. Example of a typical long running tasks could be network operations, reading files form memory, animations, etc.

In this tutorial, we will create a sample ListView in Android that downloads data  asynchronously from the internet using a AsyncTask. As you can see in the screenshot below, the ListView contains a image thumbnails on each row, we will download the images asynchronously form server.

asynchronous image loader in Android ListView

1. What is Android AsyncTask

AsyncTask enables you to implement MultiThreading without get Hands dirty into threads. AsyncTask is easy to use, and it allows performing background operation in background thread and passing the results on the UI thread. If you are doing something isolated related to UI, for example downloading data for List view, go ahead and use AsyncTask.

Some of the AsyncTask characteristics are as follows

  1. AsyncTasks should ideally be used for short operations
  2. An asynchronous task is defined by 3 generic types, called Params, Progress and Result, and 4 steps, called onPreExecute, doInBackground, onProgressUpdate and onPostExecute.
  3. In onPreExecute you can define code, which need to be executed before background processing starts.
  4. The doInBackground() method contains the code which needs to be executed in background, here in doInBackground we can send results to multiple times to event thread by publishProgress() method, to notify background processing has been completed we can return results simply.
  5. The onProgressUpdate() method receives progress updates from doInBackground method, which is published via publishProgress method, and this method can use this progress update to update event thread
  6. The onPostExecute() method handles results returned by doInBackground method.
  7. If an async task not using any types, then it can be marked as Void type.
  8. An running async task can be cancelled by calling cancel(boolean) method.

The generic types used by AsyncTask are

  • Params, the type of the parameters sent to the task upon execution
  • Progress, the type of the progress units published during the background computation.
  • Result, the type of the result of the background computation.

2. Creating a Custom ListView

1.1. Define List Row Layout

In this tutorials we are planning to crate a ListView similar to the image shown below. You can use a RelativeLayout for this.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:minHeight="50dp"
    android:orientation="horizontal" >

    <ImageView
        android:id="@+id/thumbImage"
        android:layout_width="70dp"
        android:layout_height="70dp"
        android:layout_alignParentLeft="true"
        android:layout_centerVertical="true"
        android:background="@drawable/list_placeholder" />

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/thumbImage"
        android:paddingLeft="5dp"
        android:paddingTop="5dp"
        android:text=""
        android:textColor="@drawable/list_item_text_selector"
        android:textStyle="bold"
        android:typeface="sans" />

    <TextView
        android:id="@+id/reporter"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/title"
        android:layout_marginTop="5dip"
        android:layout_toRightOf="@id/thumbImage"
        android:paddingLeft="5dp"
        android:text=""
        android:textColor="@drawable/list_item_text_selector"
        android:textSize="12sp" />

    <TextView
        android:id="@+id/date"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBaseline="@+id/reporter"
        android:layout_alignBottom="@+id/reporter"
        android:layout_alignParentRight="true"
        android:paddingRight="5dp"
        android:text=""
        android:textColor="@drawable/list_item_text_selector"
        android:textSize="12sp" />

</RelativeLayout>

1.2. Creating A Custom Adapter

public class CustomListAdapter extends BaseAdapter {
	private ArrayList listData;
	private LayoutInflater layoutInflater;

	public CustomListAdapter(Context context, ArrayList listData) {
		this.listData = listData;
		layoutInflater = LayoutInflater.from(context);
	}

	@Override
	public int getCount() {
		return listData.size();
	}

	@Override
	public Object getItem(int position) {
		return listData.get(position);
	}

	@Override
	public long getItemId(int position) {
		return position;
	}

	public View getView(int position, View convertView, ViewGroup parent) {
		ViewHolder holder;
		if (convertView == null) {
			convertView = layoutInflater.inflate(R.layout.list_row_layout, null);
			holder = new ViewHolder();
			holder.headlineView = (TextView) convertView.findViewById(R.id.title);
			holder.reporterNameView = (TextView) convertView.findViewById(R.id.reporter);
			holder.reportedDateView = (TextView) convertView.findViewById(R.id.date);
			holder.imageView = (ImageView) convertView.findViewById(R.id.thumbImage);
			convertView.setTag(holder);
		} else {
			holder = (ViewHolder) convertView.getTag();
		}

		NewsItem newsItem = (NewsItem) listData.get(position);
		holder.headlineView.setText(newsItem.getHeadline());
		holder.reporterNameView.setText("By, " + newsItem.getReporterName());
		holder.reportedDateView.setText(newsItem.getDate());

		if (holder.imageView != null) {
			new ImageDownloaderTask(holder.imageView).execute(newsItem.getUrl());
		}
		return convertView;
	}

	static class ViewHolder {
		TextView headlineView;
		TextView reporterNameView;
		TextView reportedDateView;
		ImageView imageView;
	}
}

1.3. Adding ListView to activity

Main Activity Layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <ListView
        android:id="@+id/custom_list"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:dividerHeight="1dp"
        android:listSelector="@drawable/list_selector_flatcolor" />

</LinearLayout>

Here is my activity code

public class MainActivity extends Activity {

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		ArrayList image_details = getListData();
		final ListView lv1 = (ListView) findViewById(R.id.custom_list);
		lv1.setAdapter(new CustomListAdapter(this, image_details));
		lv1.setOnItemClickListener(new OnItemClickListener() {

			@Override
			public void onItemClick(AdapterView<?> a, View v, int position, long id) {
				Object o = lv1.getItemAtPosition(position);
				NewsItem newsData = (NewsItem) o;
				Toast.makeText(MainActivity.this, "Selected :" + " " + newsData,
						Toast.LENGTH_LONG).show();
			}
		});
	}

	private ArrayList getListData() {
		ArrayList results = new ArrayList();
		NewsItem newsData = new NewsItem();
		newsData.setHeadline("Dance of Democracy");
		newsData.setReporterName("Pankaj Gupta");
		newsData.setDate("May 26, 2013, 13:35");
		newsData.setUrl("http://lh5.ggpht.com/_hepKlJWopDg/TB-_WXikaYI/AAAAAAAAElI/715k4NvBM4w/s144-c/IMG_0075.JPG");
		results.add(newsData);
                
                //Add some more dummy data to test this app.
		return results;
	}
}

3. Asynchronous ListView Example

3.1. Downloading Image From Web

static Bitmap downloadBitmap(String url) {
	final AndroidHttpClient client = AndroidHttpClient.newInstance("Android");
	final HttpGet getRequest = new HttpGet(url);
	try {
		HttpResponse response = client.execute(getRequest);
		final int statusCode = response.getStatusLine().getStatusCode();
		if (statusCode != HttpStatus.SC_OK) {
			Log.w("ImageDownloader", "Error " + statusCode
					+ " while retrieving bitmap from " + url);
			return null;
		}

		final HttpEntity entity = response.getEntity();
		if (entity != null) {
			InputStream inputStream = null;
			try {
				inputStream = entity.getContent();
				final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
				return bitmap;
			} finally {
				if (inputStream != null) {
					inputStream.close();
				}
				entity.consumeContent();
			}
		}
	} catch (Exception e) {
		// Could provide a more explicit error message for IOException or
		// IllegalStateException
		getRequest.abort();
		Log.w("ImageDownloader", "Error while retrieving bitmap from " + url);
	} finally {
		if (client != null) {
			client.close();
		}
	}
	return null;
}

3.2. Downloading Image Using AsyncTask

class ImageDownloaderTask extends AsyncTask<String, Void, Bitmap> {
	private final WeakReference imageViewReference;

	public ImageDownloaderTask(ImageView imageView) {
		imageViewReference = new WeakReference(imageView);
	}

	@Override
	// Actual download method, run in the task thread
	protected Bitmap doInBackground(String... params) {
		// params comes from the execute() call: params[0] is the url.
		return downloadBitmap(params[0]);
	}

	@Override
	// Once the image is downloaded, associates it to the imageView
	protected void onPostExecute(Bitmap bitmap) {
		if (isCancelled()) {
			bitmap = null;
		}

		if (imageViewReference != null) {
			ImageView imageView = imageViewReference.get();
			if (imageView != null) {

				if (bitmap != null) {
					imageView.setImageBitmap(bitmap);
				} else {
					imageView.setImageDrawable(imageView.getContext().getResources()
							.getDrawable(R.drawable.list_placeholder));
				}
			}

		}
	}

}

4. Output

5. Download Complete Example

Here you can download complete eclipse project source code from GitHub.

Download Code

 

6. References

http://developer.android.com/reference/android/os/AsyncTask.html

A blogger, a bit of tech freak and a software developer. He is a thought leader in the fusion of design and mobile technologies. Follow him on Twitter or Google plus.

  • 杰民 赵

    down code link failure , you can come here https://github.com/javatechig/Advance-Android-Tutorials

    • http://javatechig.com/ JavaTechig

      Yes it is currently taking to github link. The project is available on git.

  • peeyush singhal

    hello sir, i have a problem when i am running this application it shows only R.drawable.list_placeholde image. How can i solve this problem please suggest me??

  • bbb

    -1

  • ali AS

    Hi sir,Its a great tutuorial but link is broken.Could you provide a new one please

  • wallaceharris221@gmail.com

    hey plz give me the download link

  • Nagul

    this wont work correctly. Since you are using viewholder the views are recycled so if your asynctask is downloading an image and you scroll the list down, you wound end up with a wrong image at the wrong place.

    • Milan Maharjan

      yep, he’s right

  • Gabriel Peixoto

    I changed the size of images to 300dp. So, I found a bug. The List showed repeated images and in wrong position.

  • Kevin

    Hello, thanks for this code, but unfortunately I have a problem. GetView method is not called although my size is not null. Can you help me?
    Regards,
    K.L

  • Michael

    Hey, Could you give a link to github ?? I’ve liked this article but the content is still locked :/ It ll be very helpful to me.
    regards,
    Michael

  • Nilanchala Panigrahy

    Can you clear with your question. I cant guess what you asking..

  • avo28

    hello, thanks for this tutorial I worked perfect, but I have a problem, I’m using Spanish text, and accepted and the letter “Ñ” default character out with strangers, because you use clear text. java and do not know how to bring it from string. xml, I could tell how to fix this .. am newbie please calmly .. Note: I’m sorry for bad translation yet translating

  • avo28

    hola, gracias por este tutorial me funciono perfecto, pero tengo un problema, estoy utilizando texto en español, y sus aceptos y la letra “Ñ” sale con carcateres extraños, obvio porque usas texto en .java y no se como traerlo desde string.xml, me podrian decir como solucionar esto.. por favor soy novato con calma.. nota: estoy traduciendo perdon por mala traducion

  • Jyotika

    nice tutorial… its is easy to understand :)

  • Alejandro Casanova

    Thanks, excellent tutorial. The solution works just fine, but I have a problem when loading contact images, when I scroll the ListView the imageview of a row begins to change from the first contact image until de last one that was once in that position. I tried to initially put the default image to show but it only appears for a short time, then begins to change until the last one finishes loading.

    Here is my modification, but it doesn’t solves the problem completely.

    if (holder.image != null) {

    holder.image.setImageDrawable(getContext().getResources().

    getDrawable(R.drawable.ic_contact_unknown));

    new ImageDownloaderTask(holder.image).execute(contact);

    }

    How to avoid Imageview from reloading until the scrolling stops showing only a default image?

    Thanks.

  • http://www.facebook.com/art.match.5 Art Match

    Thank you for sharing. It helped me..

  • Akuma Manalo

    hi Sir,thanks for this tutorial,I would like to ask how to insert the current date automatically in listview? thanks

    • http://javatechig.com/ javatechig

      You can call getCurrentTime method from getView method.

      holder.reportedDateView.setText(getCurrentTime());


      public String getCurrentTime() {
      SimpleDateFormat sdfDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //dd/MM/yyyy
      Date now = new Date();
      String strDate = sdfDate.format(now);
      return strDate;
      }

      • Akuma Manalo

        thanks this works =)