Android provides some easy to use tools for mapping data from your application’s database into a ListView. However, you can quickly reach the limit of the basic tools as soon as you try to move past simply displaying data as it appears in your database. In this article, I discuss how to go beyond basic display of data in ListViews.
Lists, Cursors and the SimpleCursorAdapter
The SimpleCursorAdapter provides a simple way to map database fields into an entry in a List. All you have to do is provide a Cursor to your database table with the fields you want to display, map the fields to ids in your list item layout xml file and you are done: // Get all of the items from the database and create a list
Cursor cursor = mDbHelper.fetchAllItems();
startManagingCursor(cursor);
// Create a mapping between database columns and layout fields
String[] from = new String[] { DbAdapter.COL_TITLE };
int[] to = new int[] {R.id.text1};
// Create a cursor adapter and set it to display using a row layout
SimpleCursorAdapter notes =
new SimpleCursorAdapter(this, R.layout.list_item, cursor,
from, to);
setListAdapter(notes); // assumes this is a ListArray class
Cursor cursor = mDbHelper.fetchAllItems();
startManagingCursor(cursor);
// Create a mapping between database columns and layout fields
String[] from = new String[] { DbAdapter.COL_TITLE };
int[] to = new int[] {R.id.text1};
// Create a cursor adapter and set it to display using a row layout
SimpleCursorAdapter notes =
new SimpleCursorAdapter(this, R.layout.list_item, cursor,
from, to);
setListAdapter(notes); // assumes this is a ListArray class
This approach works great for a basic list of text items or numbers, but what if you want to do something beyond just displaying database data as-is? Consider the following list of things you might want to do when displaying your data in a List:
- Format the data for display
- Optionally display data for each list item (skip display of some items)
- Combine data from multiple data fields into a single display field
ComplexCursorAdapter?
So what is the next step up? No, not ComplexCursorAdapter; I made that up. The next step is to extend either the CursorAdapter or ResourceCursorAdapter classes. For this tutorial, I’ll focus on the use of ResourceCursorAdapter, which provides the ability to use XML-based layout resources for your list items.Getting Started
This tutorial assumes you have already successfully created a few Android applications. So, if you have not done that yet, get Eclipse and the Android SDK, build Hello Android and the Notepad example applications and come back.Create a new Android project with a ListActivity as the primary Activity for the application. If you want to follow along with my example project, you must choose Android SDK version 2.2 (8) or higher as your build target. After the Android Project Wizard creates the template Activity class, modify it to extend ListActivity (and make sure to update your imports):
public class CustomDataList extends ListActivity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}
Next, modify the default main.xml layout to include a ListView. (See the example project for an example.) If you are using your own layout, make sure your ListView includes the default id for lists:
<ListView
android:id="@android:id/list" ...
android:id="@android:id/list" ...
Create a layout xml for your list items under the ProjectName > res > layout. Make sure your layout has at least two TextView fields and make a note of their ids. If you need something to get started, check out the list_item_with_description.xml in the example project.
In your ListActivity class, create a new inner class that extends ResourceCursorAdapter. This inner class is the custom CursorAdapter we will use to manage how data is bound to a list item:
private class MyListAdapter extends ResourceCursorAdapter {
public MyListAdapter(Context context, Cursor cursor) {
super(context, R.layout.list_item_with_description, cursor);
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
// TODO Auto-generated method stub
}
}
public MyListAdapter(Context context, Cursor cursor) {
super(context, R.layout.list_item_with_description, cursor);
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
// TODO Auto-generated method stub
}
}
The first method is the constructor, where we declare which list item layout to use. The bindView method is where the action happens. In this method, we take control over how data from our database gets populated into each list item.
Got Data?
In order to do data binding, we need data. So, use the DbAdapter class in the example project for sample data (recommended) or use a database adapter from one of your applications. If you use your own database adapter, make sure you have a method that returns a Cursor object with three or more data fields. In the example DbAdapter class, this method is called fetchListItems().Connecting the Custom ListAdapter
Now we have a database and method to get a Cursor to our data, so it is time to connect the data pipes. In your ListActivity class, add the following to the onCreate() method: /** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
DbAdapter dbHelper = new DbAdapter(this);
dbHelper.open();
// Get a Cursor for the list items
Cursor listCursor = dbHelper.fetchListItems();
startManagingCursor(listCursor);
// set the custom list adapter
setListAdapter(new MyListAdapter(this, listCursor));
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
DbAdapter dbHelper = new DbAdapter(this);
dbHelper.open();
// Get a Cursor for the list items
Cursor listCursor = dbHelper.fetchListItems();
startManagingCursor(listCursor);
// set the custom list adapter
setListAdapter(new MyListAdapter(this, listCursor));
}
These statements create a connection to your database, get a Cursor to your list data and then set the customized ListAdapter, which tells the ListView how to render your data. Actually, we have not defined how the data is rendered for each list item yet; That is our next step.
Note: If you are getting a “method setListAdapter() is undefined” error, make sure the enclosing class extends ListActivity. This method is not available in a regular Activity class.
Binding it Your Way
With the implementation so far, no data is shown in our list! Now it is time to fix that problem by defining a data binding in the bindView() method of our custom ResourceCursorAdapter inner class. Here is how we update bindView() to render the data from the example database:@Override
public void bindView(View view, Context context, Cursor cursor) {
TextView title = (TextView) view.findViewById(R.id.item_title);
title.setText(cursor.getString(
cursor.getColumnIndex(DbAdapter.COL_TITLE)));
TextView details = (TextView) view.findViewById(
R.id.item_details);
StringBuffer detailsText = new StringBuffer();
int price = cursor.getInt(cursor.getColumnIndex( DbAdapter.COL_PRICE));
if (price > 0){
detailsText.append("$"+price+".00");
} else {
detailsText.append("Price Unavailable");
}
String description = cursor.getString(cursor.getColumnIndex(
DbAdapter.COL_DESCRIPTION));
if (description != null && description.length() > 0){
detailsText.append(", "+description);
}
details.setText(detailsText.toString());
}
public void bindView(View view, Context context, Cursor cursor) {
TextView title = (TextView) view.findViewById(R.id.item_title);
title.setText(cursor.getString(
cursor.getColumnIndex(DbAdapter.COL_TITLE)));
TextView details = (TextView) view.findViewById(
R.id.item_details);
StringBuffer detailsText = new StringBuffer();
int price = cursor.getInt(cursor.getColumnIndex( DbAdapter.COL_PRICE));
if (price > 0){
detailsText.append("$"+price+".00");
} else {
detailsText.append("Price Unavailable");
}
String description = cursor.getString(cursor.getColumnIndex(
DbAdapter.COL_DESCRIPTION));
if (description != null && description.length() > 0){
detailsText.append(", "+description);
}
details.setText(detailsText.toString());
}
As you can see from this example, while we can still do a simple mapping of an item title data to a TextView, we also have the opportunity to do apply any rendering logic we want to the data retrieved from our database. A word of caution, however, is in order. Since the bindView() code is executed fairly frequently, it is a good idea to keep your rendering logic to a minimum, otherwise your users may experience significant pauses as your list renders.
Code Download
Here is the example project for this tutorial. You need Eclipse and the Android SDK with platform version 2.2 (8) to be able to run this project.
CustomDataList_CodeProject.zip