Adapters, in Android, can be loosely defined as “classes responsible for displaying a collection of data in the user interface”.
In more practical terms, when we want to show to screen a set of pictures, a clickable list of strings or a grid of http links…, we need an adapter. The first part of the presentation given by Roman Guy @ GoogleIO 2009 (RG09) explains very well how to correctly deploy adapters in an application, so let’s follow him and borrow some material from RG09.
This sketch illustrates the paramount role played by adapters: they sit between your data and your “screen” (in this case, the “screen” is a ListView instance that will display all the items in the data source). They are responsible for organising and drawing each single View that will be included in the list. For each item, Adapter.getView() has to be called in order to perform all this work.
As such, adapters are very often the bottleneck of an application. getView() does its magic quite quickly, say a few tens of milliseconds depending on the hardware, but calling it for a list with many hundreds of items would result in the operating system to signal the application as “not responsive” or, worse, the user to uninstall our slow application. Actually, a long list might also be impossible to load as it would use up too much memory, putting in danger the whole machine.
A toy-model displaying a collection of Views consisting of text+image would use something like this
To avoid overusing system resources, developers should exploit the Android Recycler, which allows to recycle an already existing View that was shown on screen but has later disappear as a consequence, for example, of the user scrolling the list of Views.
Android passes to getView() the View convertView, that can be non null when a View is available for recycling (i.e. it is parked in the Recycler). To re-utilize precious resources is straightforward
Note that, looking at the Android sources, this trick is always (well, where I checked) used in Adapters, but we have to remember to implement the same pattern when, for some reason, we want to override getView().
We can do better.
Looking at the code above, we can see that getView() looks for the TextView and ImageView widgets by calling findViewById(). This method is another heavy-lifter, as it has to search for the right reference to return. But if we are recycling a View, that means that this treasure hunt has already been made, and we are basically re-making the same effort. This is why it is recommended to use the “holder pattern”.
Android lets associate an object to each View: so we can exploit this feature by appending the information we have to search for the first time we create a View, and then just quickly retrieve the same information when we are recycling the View.
Using an holder class (above), we can avoid to call findViewById() each time we are dealing with a recycled object (below). The two techniques employed here permit to save resources and at the same time provide a considerable speed-up when drawing our UI (data from RG09). And now for something “completely” different. Problem: what happens when I need to override a SimpleCursorAdapter? In principle, the example above is general, but it is easier to grasp and apply for adapters that do not use cursors and that do all the work in getView(). SimpleCursorAdapter, instead, works a little bit differently as its getView(), which is defined in the CursorAdapter class up the inheritance tree (as written in SDK web-docs + I checked Android 2.1 sources), trivially implements the recycling trick, but then calls newView() in case recycling has not been possible (i.e. converView = null) and bindView() otherwise. If the holder patter is to be implemented, newView() and getView() have to manage jointly the ViewHolder class. In fact newView(), as implemented in SimpleCursorAdapter, creates an mHolders (WeakHashMap) object where to store (using findViewById) all the widget references (hash values) for a “generalized” ViewHolder (key).
Code: bindView(), when called, extracts from the WeakHashMap key relative to the current View all the references that it needs to generate the item, without using the expensive findViewById().
Code: Therefore, if we want to override a SimpleCursorAdapter, care has to be taken to preserve or re-implement this mechanism. Most importantly, a naïve overriding of only bindView() will very likely destroy the holder pattern as implemented natively (which by the way works only with TextView and ImageView).
In other words, the holder pattern can be conserved overriding newView() storing, as above, the references in a well crafted, custom ViewHolder(), as in the example from RG09. Then overriding bindView(), where the same references will be pulled out of the ViewHolder without effort.
Example: We override newView(): first we inflate a layout to create a row. Then the rest follows the example above, i.e. we stick the references in the ViewHolder instance and then glue it to the View that has to be returned by newView(). As we are extending an Adapter, findViewById has to be called from a View (a bit unusual). We can then override bindView(): we are sure that, as a consequence of out newView() overriding, an holder carrying our treasure is attached to the view which is passed to bindView, so we just simply read out the info we need to lay out our row.
We stress again that the view which is passed to bindView() is View extracted from the recycler as a consequence of the getView() method implementing the recycling trick and calling bindView() if and only if a View is available in the recycler.