Logo Icon Logo
A Crowd-sourced Cookbook on Writing Great Android® Apps
GitHub logo Twitter logo OReilly Book Cover Art

Building List-Based Applications with ListView

Author: Jim Blackler
Published? true
FormatLanguage: WikiFormat

Problem:

Many mobile applications follow a similar pattern, allowing users to browse and interact with multiple items in a list. How can developers use standard Android UI classes to quickly build an app that works the way users will expect, providing them a list-based view onto their data.

Solution:

ListView is an extremely versatile control. It is well suited to the screen size and control constraints of a mobile application, displaying information in a vertical stack of rows. This recipe shows how to set up a ListView, including rows that contain any combination of standard UI Views and perform actions on single and long-clicks.

Discussion:

ListView

Many Android applications are based around the list ListView control. It is extremely versatile; well suited to the screen size and controls available on a mobile application. It solves the problem of how to present a lot of information in a way that's quick for the user to browse. It displays information in a vertical stack of rows that the user can scroll through. As the user reaches the results towards the end of the list, more results can be generated and added. This allows results paging in a natural and intuitive manner.

From using desktop applications, people are accustomed to UI controls which allow data to be manipulated in different ways. However, on the small screen of a phone these controls may be difficult to pick out in preference to others in the same area; particularly when the display is controlled with fingers rather than a stylus.

Android's ListView helps deal with this problem by separating browsing and editing operations into separate activities. A ListView simply requires the user to press somewhere in the row, which works well on a small, finger operated screen. When the row is clicked, a new Activity can be launched that can contain further options to manipulate the data shown in the row.

Menus

ListView rows can have context or 'long click' menus associated with them. This allows a list of actions that can be performed on the data represented by the row without navigating into the new activity. For instance, a list of search results could have actions such as 'Share' or 'Report spam' associated with them. One peril of this approach is that users often are not aware of the presence of long click controls.

Paging

Another advantage of the ListView format is that it allows paging in an uncomplicated way. Paging is where all the information requested by a user cannot feasibly be shown at once. For instance, the user may be browsing their email inbox, which contains 2,000 emails; it would not be feasible to download all 2,000 from the email server. Nor would it be required as the user will probably only scan the first ten or so entries.

Most web applications handle this problem by segmenting the results into pages, and having controls at the footer to allow the user to navigate through these pages. With a ListView, the application can retrieve an initial batch of the first results, which are shown to the user in a list. When the user reaches the end of the list, a final row is seen, containing an indeterminate progress bar. As this comes into view, the application can fetch the next batch of results in the background. When they are ready to be shown, the last progress bar row is replaced with rows containing the new data. The user's view of the list is not interrupted, and new data is fetched purely on demand.

Implementation

To implement a ListView in your Android application, the first thing that is required is an activity layout to host it. This should containing a ListView control configured to take up most of the screen layout. You may wonder why a layout is required if it is to be dominated by a single full-screen ListView. The reason is that other elements such as progress bars or extra overlaid indicators may be supplied in the layout.

Base class

I would not recommend the use of ListActivity to host the view. It supplies little extra logic over a plain Activity, but using it restricts the form of the inheritance tree your application's activities can take. For instance, it is very common that all activities will inherit from a single common activity, e.g. ApplicationActivity, supplying common functionality such as 'About' or 'Help' menus. This pattern won't be possible if some activities are inherited from ListActivity and some are directly inherited from Activity.

An application controls the data added to a ListView by supplying a ListAdapter using the setListAdapter() method. There are 13 functions that a ListAdapter is expected to supply. However if a BaseAdapter is used this reduces to four, representing the minimum functionality that must be supplied. The adapter specifies the number of item rows in the list, and is expected to supply a View object to represent any item given its row number. It is also expected to return both an object and an object ID to represent any given row number. This is to aid advanced list features such as row selection (not covered by this tutorial).

The documentation steers developers to a simple version of the adapter ($name?) which simply binds array entries to text fields. I wouldn't recommend this approach as it is a dead end as far as development goes. If you need to add something more complex than just text, and you probably will, you'll have to remove the $ and convert it into a more flexible adapter type such as a $. I suggest starting with the most versatile type of ListApapter, the BaseAdapter (android.widget.BaseAdapter). This allows any layout to be specified for a row (multiple layouts can be matched to multiple row types). These layouts can contain any View elements that a layout would normally contain.

Rows are created on demand by the adapter as they come on to the screen. The adapter is expected to either inflate a View of the appropriate type, or recycle the existing View, then customize it to display a row of data.

This 'recycling' is a technique employed by the Android OS to improve performance. W hen new rows come onto the screen, the OS will pass the View of a row that has moved off the screen into the adapter method $. It is up to the method to decide whether it is appropriate to reuse that View to create the new row. For this to be the case the View has to represent the layout of the new row. One way to check this is to write the layout ID into the Tag of each View inflated with setTag(). When checking to see if it is appropriate to reuse a given View, use getTag() to see if the View was inflated with the correct type. If an application is able to recycle a view the scrolling appears to be smoother for the user because CPU time is saved inflating the view.

Another way to make scrolling smoother is to do as little as possible on the UI thread. This is the default thread that your $ method will be invoked on. If time-intensive operations need to be invoked, these can be done by creating a new background thread especially for the operation. ($example). Then when the UI thread is required again so that controls can be updated, operations can be invoked on it with $. Care must be taken to ensure the View to be modified has not been recycled for another row. This can happen if the row has moved off the screen in the time it took the operation to complete. This is quite feasible if the operation was a lengthy download operation.

Setting up a basic ListView.

Use Eclipse's New Project wizard to create a new Android project with a starting activity called MainActivity. In the main.xml layout replace the existing TextView section with following:

<ListView android:id="@+id/ListView01"
	android:layout_width="wrap_content"
	android:layout_height="fill_parent"/>

In MainActivity.onCreate() insert the following snippet at the bottom of the method. This will declare a dummy anonymous class extending BaseAdapter, and apply an instance of it to the ListView. The code illustrates the methods that need to be supplied in order to populate the ListView with data.

	    ListView listView = (ListView) findViewById(R.id.ListView01);
	    listView.setAdapter(new BaseAdapter(){

	      public int getCount() {
	        return 0;
	      }

	      public Object getItem(int position) {
	        return null;
	      }

	      public long getItemId(int position) {
	        return 0;
	      }

	      public View getView(int position, View convertView, ViewGroup parent) {
	        return null;
	      }});

By customizing the anonymous class members the developer can modify the data shown by the control. However before any data can be shown, a layout must be supplied to present the data in rows. Add a file list_row.xml to your project's res/layout directory with the following content:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content">
  <TextView android:text="@+id/TextView01" android:id="@+id/TextView01" android:layout_width="fill_parent" android:layout_height="wrap_content"/>
</LinearLayout>

Now in your MainActivity add the following static array field containing just three strings.

static String[] words = {"one", "two", "three"};

Now customize your existing anonymous BaseAdapter as follows, in order to display the contents of the words array in the ListView.

listView.setAdapter(new BaseAdapter(){

	      public int getCount() {
	        return words.length;
	      }

	      public Object getItem(int position) {
	        return words[position];
	      }

	      public long getItemId(int position) {
	        return position;
	      }

	      public View getView(int position, View convertView, ViewGroup parent) {
	        LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
	        View view = inflater.inflate(R.layout.list_row, null);
	        TextView textView = (TextView) view.findViewById(R.id.TextView01);
	        textView.setText(words[position]);
	        return view;
	      }});

getCount() is customized to return the number of items in the list. getItem() and getItemId() supply the ListView with unique objects and IDs to identify the data the rows. Finally getView() creates and customizes an Android View to represent the row. This is the most complex step, so let's break down what's done.

	        LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);

The system LayoutInflater is obtained. This is the service that creates views.

	        View view = inflater.inflate(R.layout.list_row, null);

The new layout we created earlier is inflated.

	        TextView textView = (TextView) view.findViewById(R.id.TextView01);

The TextView is located.

	        textView.setText(words[position]);

The TextView is customized with the appropriate item in the words array.

	        return view;

The view is returned to the system for display.

View Recycling

XXX

No records found.