Android Adapter Good Practices
In Android, the standard way to display a list of items is to use
ListView together with a
ListView draws the currently shown items, and the
ListAdapter provides the
ListView with the
View corresponding to each item.
The aim is to create only the necessary number of views to fill the screen, and reuse these views as soon as they disappear.
This article will explain various
ListAdapter patterns and good practices.
Let’s say you want to display a list of
BananaPhone that can be updated.
Some Android tutorials advise using an
ArrayAdapter, because it is supposedly easier. This couldn’t be any less true.
ArrayAdapter has many limitations, which limits its use in real world apps.
- Can only display text.
- Forces you to provide a list of
CharSequenceitems or to rely on the
toString()method of the given items.
- Requires that you provide a layout resource that contains only a
TextView, or that you provide a
textViewResourceIdthat corresponds to the
TextViewid in the layout hierarchy.
- Uses a lock to enable updates from background threads. This lock cannot be acquired to implement external atomic operations.
According to the Javadoc:
Don’t do this!
To use something other than TextViews for the array display, for instance, ImageViews, or to have some of data besides toString() results fill the views, override getView(int,android.view.View,android.view.ViewGroup) to return the type of view you want.
If you read the source, you’ll realize that
ArrayAdapter is designed to deal with a lot of use cases which you probably do not care about.
In most apps, it’s actually a lot simpler to implement your own
I mentioned that the
ArrayAdapter uses a lock to ensure thread safety. That’s fine, but there is an even better way: get rid of threading. Make sure your adapter is used only from one thread, the Main thread.
You can easily enforce that with a fail fast strategy:
Here is an example implementation:
A naive implementation of
getView() could be:
ListView recycles the views that are not shown any more, and gives them back through
convertView. Let’s take advantage of this:
There is still one subtle problem with
getView(): each time it is called, it retrieves
findViewById() work exactly ? Here is a simplified version:
As you can see,
findViewById() navigates through the whole view hierarchy until it finds the requested view, each time you call it.
Whether or not this is a problem is up to you. If your
ListView scrolls fine even on crap devices, don’t bother optimizing. Otherwise, start traceview and measure how much time is spent in
The ViewHolder Pattern is a well known pattern to limit the number of calls to
findViewById(). The idea is that you call it once, then store the child view references in a
ViewHolder instance that will be associated with the
convertView thanks to
Here is an alternative to the ViewHolder Pattern that you can start using with Android 4.0 (API level 15). Do not use it prior to ICS (more on this below).
Since Android 1.6, there is an overloaded version of
View.setTag(int, Object) that takes an
int key. The
key => tag association is stored in a SparseArray that belongs to the view. A key lookup is basically a binary search in an array containing the sorted keys.
By the way, the
SparseArrayjavadoc says that it is intended to be more efficient than using a HashMap to map Integers to Objects. The intent is nice, but that is quite a vague assertion. Is it more efficient in terms of space? runtime? Less GC? Under which conditions? Why does it need key ordering? E.g it could have been a hashtable implementation with int keys.
Notice how we reuse the view ids as tag keys:
As mentioned before, although the API is available since Android 1.6, you shouldn’t use it prior to Android 4.0, because the implementation wasn’t a per-view
View.setTag(int, Object) clearly wasn’t designed with the ViewHolder Pattern in mind. The
key => tag association was stored in a static
WeakHashMap using the
View object as the key. A
WeakHashMap stores weak references to its keys. The idea was that as soon as a view wasn’t referenced anywhere else then in the
WeakHashMap, the entry could be garbage collected. However, if the value of a
WeakHashMap entry contains a hard reference to its key (the view), it will never be garbage collected, and you’ll get a memory leak. More on this here, also see the issue.
There is a third way that provides better decoupling. The idea is to create a custom ViewGroup, e.g.
BananaPhoneView, for each item.
BananaPhoneView will keep the references to it child views.
BananaPhoneView is now responsible for updating
Your performance measurements may tell you that you spend too much time going through the view hierarchy when measuring and drawing. You can flatten your view hierarchy by combining components, or by creating a custom view that draws the whole item manually. That’s how the mail list in Gmail works.
If you haven’t already, take at look at Android Performance Case Study.
Adapters in Android are frightening at first, but when you get to know them, they are actually quite friendly beasts. The best way to get there is to read the Android source, as well as other apps source, such as GitHub Android, White House for Android, or ioshed.
Benoît Lubek suggested another solution on Google+.
The idea is to reproduce the
View.setTag(int, Object) ICS+ behavior as an external API, relying on
This solution is elegant, and the code looks even simpler:
I really like this approach!
You explain a difficult thing in a simple way. Thank you very much.
Clear illustration of the choice of different adapters, very practical and very useful! Thanks!
I was looking for a way to replace the the entire list of items inside an ArrayAdapter without calling clear()/addAll() which generates garbage. This looks like a good solution (referring to the updateBananas() function).
I have done a lot of research, written different adapters, duct taped and glued some together, and now that I come back to revisit ListView, I find your post! I hope to see a comparison between RecylerView and the ViewHolders you have described here. However, the BaseAdapter example has me excited to write my next Adapter, which should be far more complex than what I have done to date… Thank you for your write up here! I will be sure to share with my circles.
Great article, you’ve saved my day. Thank you very much!
I really loved your article. Great explanation! I confess I started with another approach, but when I read your article I just changed everything because it’s much more elegant!