Skip to content

Custom CursorAdapters

January 11, 2010

Hey everyone,

Now that we have the SimpleCursorAdapter figured out, when writing your Custom Cursor Adapter you’ll see where some of the parameters come into play. Here’s an example of a Custom Cursor Adapter that I built:

public class ContactListCursorAdapter extends SimpleCursorAdapter implements Filterable {

    private Context context;

    private int layout;

    public ContactListCursorAdapter (Context context, int layout, Cursor c, String[] from, int[] to) {
        super(context, layout, c, from, to);
        this.context = context;
        this.layout = layout;
    }

    @Override
    public View newView(Context context, Cursor cursor, ViewGroup parent) {

        Cursor c = getCursor();

        final LayoutInflater inflater = LayoutInflater.from(context);
        View v = inflater.inflate(layout, parent, false);

        int nameCol = c.getColumnIndex(People.NAME);

        String name = c.getString(nameCol);

        /**
         * Next set the name of the entry.
         */     
        TextView name_text = (TextView) v.findViewById(R.id.name_entry);
        if (name_text != null) {
            name_text.setText(name);
        }

        return v;
    }

    @Override
    public void bindView(View v, Context context, Cursor c) {

        int nameCol = c.getColumnIndex(People.NAME);

        String name = c.getString(nameCol);

        /**
         * Next set the name of the entry.
         */     
        TextView name_text = (TextView) v.findViewById(R.id.name_entry);
        if (name_text != null) {
            name_text.setText(name);
        }
    }

    @Override
    public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
        if (getFilterQueryProvider() != null) { return getFilterQueryProvider().runQuery(constraint); }

        StringBuilder buffer = null;
        String[] args = null;
        if (constraint != null) {
            buffer = new StringBuilder();
            buffer.append("UPPER(");
            buffer.append(People.NAME);
            buffer.append(") GLOB ?");
            args = new String[] { constraint.toString().toUpperCase() + "*" };
        }

        return context.getContentResolver().query(People.CONTENT_URI, null,
                buffer == null ? null : buffer.toString(), args, People.NAME + " ASC");
    }
}

The Custom Cursor Adapter created above extends the SimpleCursorAdapter and also implements the Filterable class (which I’ll get to later). Here you can see the importance of passing in the layout of the list row entry as in the cursor adapter when the actual views in the list are being built (via newView and bindView) and both methods use this layout to inflate the view, which you can then use to retrieve the TextView / ImageView / etc that are custom designed in your list entry row XML file.

In my example, when a view is created for the first time (via newView), you first inflate the view and retrieve the cursor WHICH YOU PASSED IN. Remember this as depending on which columns you told your cursor to return, those are the columns that you can retrieve information from in your Custom Cursor Adapter. In other words, if my cursor looked like:

Cursor c = getContentResolver().query(People.CONTENT_URI, new String[] { People._ID, People.NAME }, null, null, null);

And I tried to retrieve the People.NUMBER column from my cursor, it will return an SQL exception.

The rest should be pretty self explanatory – simply grab the data you want and do what you want with the data (i.e. calculations, grabbing images, etc) and then put them into your inflated views.

One more thing to note is that YOU MUST place something into each view (even if it is NULL). For instance, had my adapter looked like:

String name = c.getString(nameCol);

TextView name_text = (TextView) v.findViewById(R.id.name_entry);
if (name_text != null && name != null) {
      name_text.setText(name);
}

Then you’ll notice some weird behavior – namely, that you’ll see the names start shifting as you scroll up and down the list. What happens is that if you don’t instantiate and place something into your TextView (basically to act as a place holder) then in your bindView method nothing gets bound to some of the TextViews and thus the shifting. So basically, if you see stuff shifting around in your lists, then that’s a big flag for make sure you are binding things to all of your views in both your newView and bindView methods.

Finally, a little on the Filterable implementation. If you want a list that filters as the user starts to type then this is what you need to do. Once you implement the Filterable class, you’ll need to override the runQueryOnBackgroundThread() method and this code snippet:

StringBuilder buffer = null;
String[] args = null;
if (constraint != null) {
     buffer = new StringBuilder();
     buffer.append("UPPER(");
     buffer.append(People.NAME);
     buffer.append(") GLOB ?");
     args = new String[] { constraint.toString().toUpperCase() + "*" };
}

And basically the StringBuilder is just building the constraint that the cursor will run. If you want to mess with this, then you’ll need to keep:

buffer = new StringBuilder();
buffer.append("UPPER(");
// this you can change
buffer.append(") GLOB ?");

But you can customize the rest of the constraint, for instance:

buffer = new StringBuilder();
buffer.append("UPPER(");
buffer.append(People.NAME + " IS NOT NULL" + " AND " + People.NUMBER_KEY + " LIKE '630%'");
buffer.append(") GLOB ?");

And that’s it! Next post will be on BaseAdapters, and I’ll also talk a little on the comparison between these two adapters.

To see all posts on CursorAdapters, please visit http://thinkandroid.wordpress.com/category/android-tutorials/cursoradapter-tutorials/

Happy coding.

- jwei

About these ads
30 Comments leave one →
  1. Paul Teale permalink
    March 3, 2010 7:56 am

    Is this correct?

    public View newView(Context context, Cursor cursor, ViewGroup parent) {

    Cursor c = getCursor();

    Wouldn’t the cursor data be already in the parameter ‘cursor’ that is passed as a parameter?
    Why do you need to call getCursor?

    • March 3, 2010 11:41 am

      Hey Paul,

      So what I think happens is that in the constructor when you pass the cursor into the super method (i.e. super(context, layout, c, from, to) ), then the SimpleCursorAdapter class probably initializes some pointer to your cursor and hence when you call getCursor() it will return that pointer.

      However I think the cursor that is passed in is the same cursor and so you probably don’t need that line. Try it and see (and if it doesn’t work we’ll know why)

      But yea thanks for catching the redundancy!

      – jwei

  2. November 4, 2010 10:01 am

    I believe the following code from public View newView(Context context, Cursor cursor, ViewGroup parent) is a redundancy because bindView is called after and has the same code.

    21 int nameCol = c.getColumnIndex(People.NAME);
    22
    23 String name = c.getString(nameCol);
    24
    25 /**
    26 * Next set the name of the entry.
    27 */
    28 TextView name_text = (TextView) v.findViewById(R.id.name_entry);
    29 if (name_text != null) {
    30 name_text.setText(name);
    31 }

    • Aleksey Malevaniy permalink
      May 31, 2011 7:00 am

      You’re totally right.

      This code could be used either in newView() or bindView() methods. But bindView is needed only to override its super.bindView constructor (remove it).

  3. Kiran Wali permalink
    November 17, 2010 10:55 pm

    Hi jwei,

    This is very good post.

    It will be great if we get the source code zip file of the same code.

    Thanks.

    Kiran Wali.

  4. Michael Little permalink
    November 22, 2010 7:26 am

    Jason,

    I ran across your website and the content has been invaluable to me. I am new to developing Android apps and have a problem that I hope you could help me with. Basically, I have two tables from an SQLite database that I need to display in a listview. My approach was to take two queries and use mergeCursor to combine them. The problem I am having is with using SimpleCursorAdapter. It does not really allow me to display results from two tables. I saw this example and, before I adapted it to my application, wanted to get some feedback from you. One table has one column and the other has three columns. Could I display both tables in one listview using mergeCursor and a custom CursorAdapter?

    • Lisa Schurch permalink
      April 6, 2011 12:32 pm

      Michael,

      I have the same question of how to display data from two tables. Did you get an answer to this question? Also, I would like to create groupings with the data. A simple example is:
      Vegetables
      Romain
      Celery
      Fruit
      Oranges
      Can this be done with the SeparatedListAdapter? Have you ever used this?

  5. rudolf erlenbach permalink
    February 19, 2011 2:40 pm

    Hi,
    can you explain how the method runQueryOnBackgroundThread() could be applied?
    filterEditText.addTextChangedListener(new TextWatcher() {… and override the method onTextChange will unfortunately fail.

  6. Kudor Gyozo permalink
    March 21, 2011 2:22 am

    Hello what is People.CONTENT_URI. Thanks.

    • Yann permalink
      February 26, 2012 5:58 am

      Hi Kudor, since your post you probably have figured it out
      but in case you may want to look into ContentProvider,
      a class that aims to wrap a CRUD module (DB mostly but can be REST module etc ..).
      Resources are accessed through ContentResolver using a URI scheme.

  7. Petr permalink
    March 31, 2011 5:34 am

    Hi, thanks for this great tutorial. I’m trying to use Gallery instead of ListView (needing two galleries on one screen) and constantly failing.
    Will you please help with a hint?
    Many thanks, Petr

  8. alex permalink
    April 10, 2011 12:46 am

    Thank you so much for this information. Immensely useful.

  9. Dustin Steiner permalink
    May 22, 2011 11:37 pm

    As Eric Harlow already mentioned: you do not have to override newView, because the view is already created through the Resource/SimpleCursorAdapter and just inflates the view.
    The binding is best done once in bindView.

  10. savin permalink
    May 25, 2011 12:11 am

    Hi,
    Do it like this
    @Override
    public View newView(Context context, Cursor cursor, ViewGroup parent) {
    LayoutInflater inflater = LayoutInflater.from(context);
    View v=inflater.inflate(layout, parent,false);
    bindView(v, context, cursor);
    return v;
    }

    @Override
    public void bindView(View v, Context context, Cursor cursor) {
    TextView name_text = (TextView) v.findViewById(R.id.name_entry);
    if (name_text != null) {
    name_text.setText(name);

    }

    }

    Or else the rows won’t be correct for a long list, exceeding the window holding it.

    • Yann permalink
      February 26, 2012 5:39 am

      Hi Savin, that’s correct.
      View newView has to return the inflated custom resource, that’s all.
      Sometimes, the View runs out of the heap and newView is called again.
      Then, void bindView does the data bind.
      However, it is not necessary to explicitly call bindView at any time.
      It is called from the super class for each Cursor item.

      • Yann permalink
        February 26, 2012 5:48 am

        I have to clarify: bindView is not really called for each cursor item but when the ListView needs to draw more items on the screen.
        This is some sort of “pagination” that allow huge Cursors not to draw themselves entirely which would make the View object too big and unusable.

  11. Aleksey Malevaniy permalink
    May 31, 2011 7:11 am

    Thanks for this WORKING tutorial!

  12. July 23, 2011 8:19 pm

    Hugely useful! One problem though, my newView method is fine, however on bindView Eclipse is throwing an error that I must override or implement a supertype method.

    newView and bindView are both set to run the exact same code (set a few text boxes, and set an ImageView resource) and both have the @Override tag on, but Eclipse is only throwing and error on bindView. Any ideas? I’ve already tried a “Clean Project” since it’s fixed similar issues for me before, but to no avail.

  13. Diego permalink
    October 30, 2011 7:08 am

    Hi, I strongly recommend you to attach a couple of screenshots of the running app.

    In order to make the examples more clear and show what is the desire result.

    Regards,

    Diego.

    • November 2, 2011 10:22 am

      Hey Diego,

      Thanks for the suggestion – makes sense to me so I’ll try and do that going forward.

      – jwei

  14. March 15, 2012 3:04 pm

    Thanks for this great how to. I need to load some thumbnail images into my list activity and with your sample code it was very easy to realize :-)

  15. Gokul permalink
    March 18, 2012 7:26 pm

    This is one of the best tutorials I’ve seen on custom cursor adapters in conjunction with the filterable class.

    What if there are two EditText fields which filter two different columns of data from the same table?

    Is there some way to specify which column to match the constraint against instead of hard-coding it?

  16. rohit permalink
    November 9, 2012 1:03 am

    thanks that helped

  17. December 7, 2012 9:19 pm

    Hi! I simply want to give you a big thumbs up for the excellent info you have right here on this post. I am coming back to your site for more soon.

Trackbacks

  1. Think Android
  2. Think Android
  3. Think Android
  4. PN.Truong Phuc's Blog
  5. Filter contacts by phone number - Android Forums
  6. setText in a ListFragment : Android Community - For Application Development

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

Follow

Get every new post delivered to your Inbox.

Join 873 other followers

%d bloggers like this: