Asynchronous Image Loader in Android ListView

Share on Google+411Tweet about this on Twitter110Share on Facebook150Share on LinkedIn5

1. Introduction

A good practice to bring the best performance for android application is to make sure your main thread does the minimum amount of work. Any long running tasks or heavy operations are usually performed in a different thread. Typical long running tasks could be network operations, reading files form memory, animations, etc. In this article, we will create a simple asynchronous ListView in Android. ListView with thumbnails thumbnails on each row (as shows in the image below) will be loaded asynchronously form server. We will populate a ListView with thumbnail images downloaded from the internet using a AsyncTask.

asynchronous image loader in Android ListView

1. Understanding AsyncTask in Android

AsyncTask enables you to implement MultiThreading without get Hands dirty into threads. AsyncTask enables proper and easy use of the UI thread. It allows performing background operations and passing the results on the UI thread. If you are doing something isolated related to UI, for example downloading data to present in a list, go ahead and use AsyncTask.

  • AsyncTasks should ideally be used for short operations (a few seconds at the most.)
  • An asynchronous task is defined by 3 generic types, called Params, Progress and Result, and 4 steps, called onPreExecute, doInBackground, onProgressUpdate and onPostExecute.
  • In onPreExecute you can define code, which need to be executed before background processing starts.
  • doInBackground have 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.
  • 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
  • onPostExecute() method handles results returned by doInBackground method.
  • The generic types used 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.
  • If an async task not using any types, then it can be marked as Void type.
  • An running async task can be cancelled by calling cancel(boolean) method.

2. Creating a Custom ListView with thumbnails

1.1. Creating a custom 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

import java.util.ArrayList;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import com.example.customizedlist.R;

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

import java.util.ArrayList;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import android.widget.Toast;
import com.example.customizedlist.R;

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);

		newsData = new NewsItem();
		newsData.setHeadline("Major Naxal attacks in the past");
		newsData.setReporterName("Pankaj Gupta");
		newsData.setDate("May 26, 2013, 13:35");
		newsData.setUrl("http://lh4.ggpht.com/_4f1e_yo-zMQ/TCe5h9yN-TI/AAAAAAAAXqs/8X2fIjtKjmw/s144-c/IMG_1786.JPG");
		results.add(newsData);

		newsData = new NewsItem();
		newsData.setHeadline("BCCI suspends Gurunath pending inquiry ");
		newsData.setReporterName("Rajiv Chandan");
		newsData.setDate("May 26, 2013, 13:35");
		newsData.setUrl("http://lh3.ggpht.com/_GEnSvSHk4iE/TDSfmyCfn0I/AAAAAAAAF8Y/cqmhEoxbwys/s144-c/_MG_3675.jpg");
		results.add(newsData);

		newsData = new NewsItem();
		newsData.setHeadline("Life convict can`t claim freedom after 14 yrs: SC");
		newsData.setReporterName("Pankaj Gupta");
		newsData.setDate("May 26, 2013, 13:35");
		newsData.setUrl("http://lh6.ggpht.com/_ZN5zQnkI67I/TCFFZaJHDnI/AAAAAAAABVk/YoUbDQHJRdo/s144-c/P9250508.JPG");
		results.add(newsData);

		newsData = new NewsItem();
		newsData.setHeadline("Indian Army refuses to share info on soldiers mutilated at LoC");
		newsData.setReporterName("Pankaj Gupta");
		newsData.setDate("May 26, 2013, 13:35");
		newsData.setUrl("http://lh4.ggpht.com/_XjNwVI0kmW8/TCOwNtzGheI/AAAAAAAAC84/SxFJhG7Scgo/s144-c/0014.jpg");
		results.add(newsData);

		newsData = new NewsItem();
		newsData.setHeadline("French soldier stabbed; link to Woolwich attack being probed");
		newsData.setReporterName("Sudeep Nanda");
		newsData.setDate("May 26, 2013, 13:35");
		newsData.setUrl("http://lh6.ggpht.com/_Nsxc889y6hY/TBp7jfx-cgI/AAAAAAAAHAg/Rr7jX44r2Gc/s144-c/IMGP9775a.jpg");
		results.add(newsData);

		newsData = new NewsItem();
		newsData.setHeadline("Life convict can`t claim freedom after 14 yrs: SC");
		newsData.setReporterName("Pankaj Gupta");
		newsData.setDate("May 26, 2013, 13:35");
		newsData.setUrl("http://lh6.ggpht.com/_ZN5zQnkI67I/TCFFZaJHDnI/AAAAAAAABVk/YoUbDQHJRdo/s144-c/P9250508.JPG");
		results.add(newsData);

		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);

		return results;
	}
}

3. Implementing Asynchronous ListView Android Image Loader

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 android AsyncTask

import java.io.InputStream;
import java.lang.ref.WeakReference;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.HttpGet;
import com.example.customizedlist.R;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.http.AndroidHttpClient;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.ImageView;

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.

[sociallocker]

Download Code

[/sociallocker]

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.

-- Advertisement --
  • 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 =)