6 min read

(For more resources on Android, see here.)

SimpleCursorAdapters and ListViews

There are two major ways of retrieving data on Android, and each has its own class of ListAdapters, which will then know how to handle and bind the passed-in data. The first way of retrieving data is one that we’re very familiar with already – through making queries and obtaining Cursor objects. The subclass of ListAdapters that wrap around Cursors is called CursorAdapter, and in the next section we’ll focus on the SimpleCursorAdapter, which is the most straightforward instance of CursorAdapter.

The Cursor points to a subtable of rows containing the results of our query. By iterating through this cursor, we are able to examine the fields of each row. Now we would like to convert each row of the subtable into a corresponding row in our list. The first step in doing this is to set up a ListActivity (a variant of the more common Activity class).

As its name suggests, a ListActivity is simply a subclass of the Activity class which comes with methods that allow you to attach ListAdapters. The ListActivity class also allows you to inflate XML layouts, which contain list tags. In our example, we will use a very bare-bones XML layout (named list.xml) that only contains a ListView tag as follows:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android_orientation="vertical"
android_layout_width="fill_parent"
android_layout_height="wrap_content" >
<ListView
android_id="@android:id/android:list"
android_layout_width="fill_parent"
android_layout_height="wrap_content" />
</LinearLayout>

This is the first step in setting up what’s called a ListView in Android. Similar to how defining a TextView allows you to see a block of text in your Activity, defining a ListView will allow you to interact with a scrollable list of row objects in your Activity.

Intuitively, the next question in your mind should be: Where do I define how each row actually looks? Not only do you need to define the actual list object somewhere, but each row should have its own layout as well. So, to do this we create a separate list_entry.xml file in our layouts directory.

The example I’m about to use is the one that queries the Contacts content provider and returns a list containing each contact’s name, phone number, and phone number type. Thus, each row of my list should contain three TextViews, one for each data field. Subsequently, my list_entry.xml file looks like the following:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android_orientation="vertical"
android_layout_width="fill_parent"
android_layout_height="wrap_content"
android_padding="10dip" >
<TextView
android_id="@+id/name_entry"
android_layout_width="wrap_content"
android_layout_height="wrap_content"
android_textSize="28dip" />
<TextView
android_id="@+id/number_entry"
android_layout_width="wrap_content"
android_layout_height="wrap_content"
android_textSize="16dip" />
<TextView
android_id="@+id/number_type_entry"
android_layout_width="wrap_content"
android_layout_height="wrap_content"
android_textColor="#DDD"
android_textSize="14dip" />
</LinearLayout>

So we have a vertical LinearLayout which contains three TextViews, each with its own properly defined ID as well as its own aesthetic properties (that is, text size and text color).

In terms of set up – this is all we need! Now we just need to create the ListActivity itself, inflate the list.xml layout, and specify the adapter. To see how all this is done, let’s take a look at the code before breaking it apart piece by piece:

public class SimpleContactsActivity extends ListActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.list);
// MAKE QUERY TO CONTACT CONTENTPROVIDER
String[] projections = new String[] { Phone._ID,
Phone.DISPLAY_NAME, Phone.NUMBER, Phone.TYPE };
Cursor c = getContentResolver().query(Phone.CONTENT_URI,
projections, null, null, null);
startManagingCursor(c);
// THE DESIRED COLUMNS TO BE BOUND
String[] columns = new String[] { Phone.DISPLAY_NAME,
Phone.NUMBER, Phone.TYPE };
// THE XML DEFINED VIEWS FOR EACH FIELD TO BE BOUND TO
int[] to = new int[] { R.id.name_entry, R.id.number_entry,
R.id.number_type_entry };
// CREATE ADAPTER WITH CURSOR POINTING TO DESIRED DATA
SimpleCursorAdapter cAdapter = new SimpleCursorAdapter(this,
R.layout.list_entry, c, columns, to);
// SET THIS ADAPTER AS YOUR LIST ACTIVITY'S ADAPTER
this.setListAdapter(cAdapter);
}
}

So what’s going on here? Well, the first part of the code you should recognize by now – we’re simply making a query over the phone’s contact list (specifically over the Contact content provider’s Phone table) and asking for the contact’s name, number, and number type.

Next, the SimpleCursorAdapter takes as two of its parameters, a string array and an integer array which represent a mapping between Cursor columns and XML layout views. In our case, this is as follows:

// THE DESIRED COLUMNS TO BE BOUND
String[] columns = new String[] { Phone.DISPLAY_NAME, Phone.NUMBER,
Phone.TYPE };
// THE XML DEFINED VIEWS FOR EACH FIELD TO BE BOUND TO
int[] to = new int[] { R.id.name_entry, R.id.number_entry, R.id.
number_type_entry };

This is so that the data in the DISPLAY_NAME column will get bound to the TextView with ID name_entry, and so on. Once we have these mappings defined, the next part is to just instantiate the SimpleCursorAdapter, which can be seen in this line:

// CREATE ADAPTER WITH CURSOR POINTING TO DESIRED DATA
SimpleCursorAdapter cAdapter = new SimpleCursorAdapter(this, R.layout.
list_entry, c, columns, to);

Now, the SimpleCursorAdapter takes five parameters – the first is the Context, which essentially tells the CursorAdapter which parent Activity it needs to inflate and bind the rows to. The next parameter is the ID of the R layout that you defined earlier, which will tell the CursorAdapter what each row should look like and, furthermore, where it can inflate the corresponding Views. Next, we pass in the Cursor, which tells the adapter what the underlying data actually is, and lastly, we pass in the mappings.

Hopefully, the previous code makes sense, and the parameters of SimpleCursorAdapter make sense as well. The result of this previous Activity can be seen in the following screenshot:

DataForAndroidExamples

Everything looks good, except for these random integers floating around under the phone number. Why are there a bunch of 1s, 2s, and 3s at the bottom of each row where the types should be? Well, the phone number types are not returned as Strings but are instead returned as integers. From there through a simple switch statement, we can easily convert these integers into more descriptive Strings.

However, you’ll quickly see that with our very simple, straightforward use of the built-in SimpleCursorAdapter class, there was nowhere for us to implement any “special” logic that would allow us to convert such returned integers to Strings. This is when overriding the SimpleCursorAdapter class becomes necessary, because only then can we have full control over how the Cursor’s data is to be displayed in each row. And so, we move onwards to the next section where we see just that.

LEAVE A REPLY

Please enter your comment!
Please enter your name here