CursorAdapter: plug a Cursor into a SimpleCursorAdapter

The second screenshot shows a ListView. Click on an item to pop up a piece of Toast. The position numbers are the zero-based positions of each item in the ListView; Curly is number 0 since he is at the top of the list in alphabetical order. The id numbers are the one-based is numbers of each item in the database; Curly is number 3 since I created Moe and Larry before him.

Before I ran this app, I used the People app to create contacts named Moe, Larry, Curly, Shemp, in that order. To keep it simple, I gave each contact no information besides its name. There are no addresses, phone numbers, etc.

We saw a Cursor here. We saw a ListView whose adapter is a ArrayAdapter<String> here. The next step is a ListView whose adapter is a SimpleCursorAdapter containing a Cursor. This will probably be how you display records from an SQLite database. A Cursor in an adapter must read the field named _id. That means that the projection array must contain this field. The fourth and fifth arguments of the SimpleCursorAdapter’s constructor must be arrays of the same length.

CursorAdapter is an abstract class; we would have to define many of its methods. SimpleCursorAdapter is a concrete (non-abstract) class; all of its methods are already defined with stubs.

The position argument of onListItemClick is the position of the item in the ListView. The id argument is the position of the item in the adapter, which in this case is the _id field of the SQLite database that holds the contacts. They are different because the positions start at 0 while the _ids start at 1. They are also different because of the sortOrder passed to the constructor of the CursorLoader.

Since the data in this list comes from a database, getItemAtPosition returns a record in the database. It does this by returning a Cursor with which the record can be read.

Source code in CursorAdapter.zip

  1. MainActivity.java
  2. R.java
  3. activity_main.xml. The RelativeLayout contains a ListView.
  4. AndroidManifest.xml has android.permission.READ_CONTACTS.
  5. build.gradle (Module: app).

simple_list_item_1.xml

Each line of the ListView is displayed as the TextView in the following file
~/Library/Android/sdk/platforms/android-22/data/res/layout/simple_list_item_1.xml
(shown without its <?xml> tag and copyright notice). The identifying number of this file is
android.R.layout.simple_list_item_1
This id number starts with android.R because the file is on your hard disk. A file that is part of your app (e.g., activity_main.xml) has an id number that starts with R (e.g., R.layout.activity_main).

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/text1"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:textAppearance="?android:attr/textAppearanceListItemSmall"
    android:gravity="center_vertical"
    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
    android:minHeight="?android:attr/listPreferredItemHeightSmall" />

SimpleCursorAdapter and ListView in ApiDemos

See Using a Loader for another example of not using a Cursor until a load is completed.

  1. App → Loader → Cursor
    1. ApiDemos/src/com/example/android/apis/app/LoaderCursor.java
      Click on the magnifying glass in the action bar and search for the name of one of your contacts. (The widget where you type in the name is called a SearchView.) The Activity creates a Fragment, specifically a ListFragment whose user interface is a built-in ListView. The ListFragment creates a SimpleCursorAdapter without a Cursor. Later, when onCreateLoader is called, it creates a CursorLoader. Still later, when onLoadFinished is called, it creates a Cursor that is swapped into the SimpleCursorAdapter.

Things to try

  1. Display two columns of the database. The file
    ~/Library/Android/sdk/platforms/android-22/data/res/layout/simple_list_item_2.xml
    contains the following TwoLineListItem. The second TextView can have android:layout_below and android:layout_alignStart because TwoLineListItem is a subclass of relativeLayout. <?xml> and copyright omitted. The android:id attributes need no plus signs because the values android.R.id.text1 and android.R.id.text2 are built into the SDK.
    <TwoLineListItem xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:minHeight="?attr/listPreferredItemHeight"
        android:mode="twoLine"
        android:paddingStart="?attr/listPreferredItemPaddingStart"
        android:paddingEnd="?attr/listPreferredItemPaddingEnd">
    
        <TextView android:id="@id/text1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:textAppearance="?attr/textAppearanceListItem" />
    
        <TextView android:id="@id/text2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/text1"
            android:layout_alignStart="@id/text1"
            android:textAppearance="?attr/textAppearanceListItemSecondary" />
    
    </TwoLineListItem>
    
            SimpleCursorAdapter adapter = new SimpleCursorAdapter(
                this,
                android.R.layout.simple_list_item_2,
                null,
                new String[] {ContactsContract.Contacts.DISPLAY_NAME, ContactsContract.Contacts._ID},
                new int[]    {android.R.id.text1,                     android.R.id.text2},
                0	//flags
            );
    

    Since class TwoLineListItem is now deprecated, you could create the following file simple_list_item_2.xml containing a LinearLayout in place of the TwoLineListItem. I had to change some of the attributes in the above file simple_list_item_2.xml to get them to compile for older versions of Android.

    In the Android Studio project view, select the folder app/res/layout and pull down
    File → New… → Layout resource file
    New Resource File
    File name: simple_list_item_2
    Root element: LinearLayout
    OK

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:minHeight="?attr/listPreferredItemHeight"
        android:paddingLeft="?attr/listPreferredItemPaddingLeft"
        android:paddingRight="?attr/listPreferredItemPaddingRight">
    
        <TextView
            android:id="@+id/text1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:textAppearance="?attr/textAppearanceListItem"/>
    
        <TextView
            android:id="@+id/text2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textAppearance="?attr/textAppearanceListItemSmall"/>
    
    </LinearLayout>
    
            SimpleCursorAdapter adapter = new SimpleCursorAdapter(
                this,
                R.layout.simple_list_item_2,
                null,
                new String[] {ContactsContract.Contacts.DISPLAY_NAME, ContactsContract.Contacts._ID},
                new int[]    {R.id.text1,                             R.id.text2},
                0	//flags
            );
    

  2. Print the names in all uppercase, even though they are lowercase (except for the initial letter) in the database. Until now, the SimpleCursorAdapter has called setText to set the text of each TextView. But now we will call the setText ourselves for the TextViews in the display name column. Insert the following code after creating the SimpleCursorAdapter.
            adapter.setViewBinder(new SimpleCursorAdapter.ViewBinder() {
    
                //This method will be called for every column of every row.
                //Most of the time it will do nothing and just return false.
                //But for each TextView belonging to the display name column,
                //it will set the text of the TextView and return true.
    
                @Override
                public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
                    int displayNameIndex = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME);
                    if (columnIndex == displayNameIndex) {
                        String displayName = cursor.getString(displayNameIndex);
                        ((TextView)view).setText(displayName.toUpperCase());
                        return true; //Indicates that this method has written the text into the TextView.
                    }
                    return false;    //Indicates that the text still needs to be written into the TextView.
                }
            });