Working with Android ListViews

Lists are an integral way to communicate information about a set of data the user is interested in. All mobile platforms provide different ways to define and render lists to the user.  In Android the ListView widget is used for this purpose.  The widget allows for many features but some that I feel are most important are:

  • The Adapter
  • View Types
  • View Generation
  • Updating

These concepts are integral to constructing efficient lists that can handle data efficiently and effectively which leads to a more positive user experience.

The Adapter

The best way to think of the Adapter is your data source, but it is much more than that.  It often contains specific logic for view generation, type declaration, and updating logic.  It is the brain behind the list itself.

The Adapter is always of type BaseAdapter, but often this is too abstract for most cases.  There are a variety of derivations of this class, you should look at see what makes the most sense for you; I tend to use ArrayAdapter so I can pass in a typed list to the adapter as the data source.  For clarity, I will focus on ArrayAdapter for the remainder of this section.

To make life easy on you, you can pass a Resource ID to the constructor of ArrayAdapter (via super) to indicate which view should be used for each entry.  Often, however, inflation is used instead, but we will cover that in ‘View Generation’.  For ArrayAdapter the parameters are as follows:

  • Context context: the activity or receivers calling the adapter.  This is often referred to as “the context of the application”
  • int resource: this is the Resource Id of the layout which will be used to display each item in the data source
  • int textViewResourceId: The Resource Id of the TextView within the layout which will be used to display the item in the list
  • T[] objects/List objects: The set of data to be displayed by the list, either in array or List format

As you will see later in ‘View Generation’, the resource approach is used for extremely simple cases and often wont fit more complex scenarios.

View Types

Often it may be necessary to provide sections or other custom formatting between items, these are referred to a different view types.  The adapter must know how many different views are being used within its list to aid in rendering.  This function is only needed if you intend to define more than one and often it amounts to returning a hard coded numbers based on the number of views being used.  Here is an example:

    @Override
    public int getViewTypeCount() {
        return 2;
    }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Very simple.

View Generation

Android, like most platforms does on demand view rendering for lists.  What this means is that if a list contains 100 items, but only 10 can be seen on the users screen, Android (via the adapter) will only render 20-30 items.  This means when we talk about view generation it cannot be emphasized enough how important efficient code is for the view rendering portion.  Never do blocking lookups within here as it will kill the user experience or, at worst, bring up the dreaded ANR (Application Not Responsive) prompt.

View generation is a double edged sword, on the one hand it gives you immense flexibility with respect to how the view looks and functions, but since it is often reliant on Layout Inflation it can make the application feel slow and unresponsive.

Layout inflation is the process of using the Layout Inflation System service to take a Resource ID and convert it into a full fledged View which can be referenced and modified programmatically.  It is important to understand that, in accordance with the rendering process above, Android will often re-use views so, NEVER assume, the view you are getting is fresh, and dont force the engine to always inflate either.  Below is a sample getView implementation which only shows how a view should be inflated to be as efficient as possible:

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if (theView == null) {
            theView = LayoutInflater.from(getContext()).inflate(R.layout.home_list_item, null);
        }

        return theView;
    }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Notice the if check around the inflater, this ensures that if a view instance is being reused it is not reinflated.  Taking this approach will allow the use of whatever view you want, even different views based on conditions (remember to update getViewTypeCount).

Once you have an instance of the view, you can operate on it like you would any other case.  For example:

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if (convertView == null) {
            convertView = LayoutInflater.from(getContext()).inflate(R.layout.home_list_item, null);
        }
        
        GameEntry game = getItem(position);
        TextView homeTeamName = (TextView) convertView.findViewById(R.id.homeTeamName);
        homeTeamName.setText(game.getHomeTeamName());

        return convertView;
    }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Here, we get the instance at the given position and assign the Home Team name as the text for a TextView that is declared within that view.

The View Holder Pattern

The inflation approach is extremely popular within Android application due to the flexibility and control it affords.  However, this comes at a price as Inflation and calls to findViewById can be very expensive.  To counter this, we present the ViewHolder pattern.  To implement this for the example above create a static inner class within your adapter with a single member, like below:

    private static class ViewHolder {
        TextView homeTeam;
    }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

The idea is to have this class sit within the convertView and be reused as a way to reference the view components within your layout.  For example, your updated logic looks like this:

    private View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder viewHolder;

        if (convertView == null) {
            convertView = LayoutInflater.from(getContext()).inflate(R.layout.home_list_item, null);
            viewHolder = new ViewHolder();

            viewHolder.homeTeam = (TextView) convertView.findViewById(R.id.homeTeam);
            convertView.setTag(viewHolder);
        }
        else {
            viewHolder = (ViewHolder) convertView.getTag();
        }

        GameEntry game = getItem(position);
        viewHolder.homeTeam.setText(gameEntry.getHomeTeam()));
        return convertView;
    }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Doing this mitigates the needs to repeatedly call findViewById which increases the rendering response as the user flicks through the list, this can be especially helpful in a longer list with more items.

Updating

Updating items within a list in Android can be a very heavy operation, especially with a larger list.  Thankfully Android provides a method to allow the List to update with a single line of code: notifyDataSetChanged().

Understand that the notifyDataSetChanged can ONLY work by looking for changes in the datasource, which will be different depending on your adapter type and how it is fed its data.  For example, if you are pulling from a repository, then simply updating your repository ahead of the call to notifyDataSetChanged will be sufficient.  If you are working with an Array Adapter realize that your data source is passed into the adapter, so updating it externally will not be reflected in your list.  Fortunately, the adapter supports add, insert, remove, and clear to allow you to update the dataset inside the ArrayAdapter ahead of notifyDataSetChanged to see your changes.

Realize that there are many other aspects which are crucial for properly implementing a ListView in Android and making it contribute positively to the user experience.  For example, implementing a list with a remote datasource requires different considerations than a static list.  Always remember the user and seek to avoid the dreaded ANR (Application Not Responding).

Advertisements

One thought on “Working with Android ListViews

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s