Handling ListView Recycle on Android

posted on March 12 2013 - 23:59
android androiddev listview view-recycling

When you want to create a ListView and face the problem of some duplicated items, and don't know how to fix. what turns out is, android recycles views when scrolling. If there is already a view object that just needs different data then that view will be passed in with the convertView parameter. If this happens the code simply returns the same view again. This explains why you see duplicated items on your ListView.

But the question is, is view recycling necessary/worth?

There are many reasons for recycling views:

Below is an example of how view recycling works. It's basically reusing the last view to be the first again. That's why we must make sure the recycling is made correctly.

View Recycling example

So how to handle this? I'm gonna show you a simple example.

We must create the xml file that will represent the row itself (i mean, the row of the listview). So it will be called list_item.xml.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal" 
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/listitem_bg" >

    <ImageView 
        android:id="@+id/imageViewContactIcon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/ic_contact_picture" />

    <TextView 
        android:id="@+id/textViewContactName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@+id/imageViewContactIcon"
        android:textStyle="bold"
        android:textSize="16dp"
        android:textColor="#444444"
        android:layout_marginTop="5dp"
        android:layout_marginLeft="5dp" />

    <TextView android:id="@+id/textViewContactNumber"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/textViewContactName"
        android:layout_toRightOf="@+id/imageViewContactIcon"
        android:textSize="12dp"
        android:textColor="#444444"
        android:layout_marginTop="5dp"
        android:layout_marginLeft="5dp" />

</RelativeLayout>

Now, let me show you how the adapter must be made so the view recycle is handled correctly.

public class MyArrayAdapter extends ArrayAdapter<Contact>
{
    Context mContext;
    List<Contact> mContactsList;

public MyArrayAdapter(Context context, int textViewResourceId, List<Contact> contacts) 
{
    super(context, textViewResourceId);
    mContext = context;
    this.mContactsList = contacts;
}

@Override
public View getView(int position, View convertView, ViewGroup parent)
{
    View row = convertView;
        ViewHolder holder = new ViewHolder();

    if(row == null)
    {
        row = mInflater.inflate(R.layout.list_item, parent, false);
            // initialize the elements
        holder.contactIcon = (ImageView) row.findViewById(R.id.imageViewContactIcon);
        holder.contactName = (TextView) row.findViewById(R.id.textViewContactName);
        holder.contactNumber = (TextView) row.findViewById(R.id.textViewContactNumber);

        row.setTag(holder);
    }
    else
    {
        holder = (ViewHolder)row.getTag();
    }

    // get the contact from the list
    Contact contact = mContactsList.get(pos);

    if(contact != null)
    {
        // get the contact picture based on the contact id 
            Bitmap contactPictureBitmap = mContactController.getContactPicture(contact.getM_id());

            holder.contactIcon.setImageBitmap(contactPictureBitmap); // set the retrieved contact picture

            holder.contactName.setText(contact.getmDisplayName() == null ? contact.getM_id().toString() : contact.getmDisplayName());
            holder.contactNumber.setText(phoneNumber.getmPhoneNumberValue());
    }

        return row;
    }
}

public class ViewHolder
{
    ImageView contactIcon;
    TextView contactName;
    TextView contactNumber;
}

So, explaining why the magic happens after the if-else statement in the getView method. as I explained before:

If there is already a view object that just needs different data then that view will be passed in with the convertView parameter. If this happens the code simply returns the same view again.

So, in getView if the row parameter is null we create it and set the holder pattern. if the row parameter is not null, we get the view from the holder, we will set the data to the holder attributes and return the view.

This way you make sure the views will contain the correct data, and you wont have any duplicated items.

I hope this post helps handle this issue, and if you want to learn more about how view recycling works, check this question on stackoverflow or watch the romain guy presentation at google IO ’09.

Please share and comment with doubts, opinions, etc. thanks!