Custom CursorAdapters
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 https://thinkandroid.wordpress.com/category/android-tutorials/cursoradapter-tutorials/
Happy coding.
– jwei
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?
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
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 }
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).
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.
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?
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?
Hi,
can you explain how the method runQueryOnBackgroundThread() could be applied?
filterEditText.addTextChangedListener(new TextWatcher() {… and override the method onTextChange will unfortunately fail.
Hello what is People.CONTENT_URI. Thanks.
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.
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
Thank you so much for this information. Immensely useful.
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.
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.
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.
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.
Thanks for this WORKING tutorial!
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.
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.
Hey Diego,
Thanks for the suggestion – makes sense to me so I’ll try and do that going forward.
– jwei
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 🙂
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?
thanks that helped
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.