The Nuance Of Android ListView Recycling (For n00bs)

When you’re learning Android you’re told that using ListViews without using ListView recycling is bad (and it is). So, like any new Android programmer you fire up google and go looking for code examples. Once you find what you’re looking for you may find yourself struggling to understand exactly what is going on. The following code snippet is the meat and potatoes of ListView recycling:

public View getView(int position, View convertView, ViewGroup parent) {
 
    ViewHolder holder = null;
 
    if (convertView == null) {
        mInflater = (LayoutInflater) mContext.getSystemService(LAYOUT_INFLATER_SERVICE);
        convertView = mInflater.inflate(R.layout.listview_item_product, null);
        holder = new ViewHolder();
        holder.mProductNameTextView =
                (TextView) convertView.findViewById(R.id.productName);
        holder.mProductIdTextView =
                (TextView) convertView.findViewById(R.id.ProductId);
        convertView.setTag(holder);
    } else {
        holder = (ViewHolder) convertView.getTag();
    }
    String id_holder = "Product ID: " + mIdList.get(position);
    holder.mProductNameTextView.setText(mNameList.get(position));
    holder.mProductIdTextView.setText(id_holder);
    return convertView;
}
 
public static class ViewHolder {
    public TextView mProductNameTextView;
    public TextView mProductIdTextView;
}

First we create ViewHolder variable we will use throughout the getView() function:

 ViewHolder holder = null;

Next the conditional statment:

 if (convertView == null) {

Why are we checking if convertView is null? Because if it is this means our ListView is being populated for the first time and the view must be inflated (created)  from our ListView item layout.:

 convertView = mInflater.inflate(R.layout.listview_item_product, null);

Next we create an instance of our ViewHolder class.

 holder = new ViewHolder();

Let’s pause here to understand a little bit more about exactly what this class is. ViewHolder is a static class which means that instances of the class act like global variables, able to avoid pesky scope issues like their local brethren. So any values loaded into a static object will persist across function calls, which comes in very handy as we will see later.  That being said, let’s proceed.

holder.mProductNameTextView = (TextView) convertView.findViewById(R.id.productName);
holder.mProductIdTextView = (TextView) convertView.findViewById(R.id.ProductId);

Here’s where things get interesting. In the above code we are taking the newly inflated layout object (convertView) and getting a reference to the TextViews that reside in that layout object with a call to findViewById(). But notice that we are actually storing the references to those TextViews in a ViewHolder object (holder). The next line is where things tend to go hazy for the noob:

convertView.setTag(holder);

Well, allow me to clear the smoke. Think of setTag() as putObject(Object ob) because that’s what it really does. You pass in any object and setTag() stores it inside the calling object and allows you to retrieve it with a call to getTag(). So what you are actually doing in the above code is storing that particular instance of ViewHolder inside convertView. Notice that with each newly inflated Layout object (convertView) we create a corresponding ViewHolder object and store it inside convertView. From here on out think of convertView and the holder as Siamese twins.

Remember that getView() is called when we first need to populate our ListView, but it’s also called whenever we are scrolling through our ListView and a new list item is about to come on screen. The system re-uses the views it inflated a few paragraphs ago and passes them to getView() as the convertView parameter. When this happens convertView will not be null, so we end up executing the following code:

} else {
    holder = (ViewHolder) convertView.getTag();
}

Remember that each of our inflated views has a Siamese twin attached to it which is an instance of our ViewHolder class. We get access to that twin by calling getTag() on the recycled view (convertView) and storing its twin ViewHolder object in the holder variable.

Let’s go back even further and try to remember what ViewHolder is here for in the first place. Instances of Viewholder hold references to their twin’s TextViews! Why is this important? Without those references we would have to call findViewById() every time a recycled view comes on screen to update the data displayed in that view. 1,000 calls to findViewById() can get very expensive! And because a ListView can be ANY size we must assume that we may have to make 1,000,000 calls to findViewById() unless we find a cheaper alternative.

This is where already having a reference, or Id, to the convertView’s TextViews is a life saver.

String id_holder = "Product ID: " + mIdList.get(position);
holder.mProductNameTextView.setText(mNameList.get(position));
holder.mProductIdTextView.setText(id_holder);

In the above code we can modify the content of convertView’s TextViews by calling methods directly on its twins TextViews – which are nothing more than pointers to convertView’s TextViews. Once you understand this the last line doesn’t seem so crazy.

return convertView;

Before reading this explanation you may have wondered “Why would I pass back convertView if I didn’t even touch it?” Now you know that you did modify it, only instead of calling findViewById() on it to change the text in it’s TextViews, you just used pointers to those TextViews that resided in its twin object, an instance of the handy ViewHolder class. I hope this helped you to better understand this topic. Thanks for reading

Shamari Feaster

About Shamari Feaster

Shamari Feaster is a 5th year senior pursuing undergraduate degrees in Computer Science and Management Information Systems at FSU. He specializes in web application development but is proficient in Android Java programming, PHP, Javascript, and C++. He is currently employed as a mobile application designer/developer.

Switch to our mobile site