Intercepting SimpleCursorAdapter data #androiddev

Normally when you create a SimpleCursorAdapter for a ListView, you specify a one-to-one mapping of the columns from the DB and the views where you want them to end up, and the adapter basically just does a toString() on your data and sticks it in the view.

You can, of course, modify this behavior by overriding setViewText, which lets you reformat the text or modify the view as you wish; but it doesn’t give you the DB cursor, just a String, so you can’t pull the data yourself or refer to other columns. But not to despair (or turn to the NotSoSimpleCursorAdapter)! You can modify anything you want by providing a ViewBinder to the adapter.

The main reason I’m talking about this is because I think the documentation on this is just a little vague:

An easy adapter to map columns from a cursor to TextViews or ImageViews defined in an XML file. You can specify which columns you want, which views you want to display the columns, and the XML file that defines the appearance of these views. Binding occurs in two phases. First, if a SimpleCursorAdapter.ViewBinder is available, setViewValue(android.view.View, android.database.Cursor, int) is invoked. If the returned value is true, binding has occured. If the returned value is false and the view to bind is a TextView, setViewText(TextView, String) is invoked. If the returned value is false and the view to bind is an ImageView, setViewImage(ImageView, String) is invoked. If no appropriate binding can be found, an IllegalStateException is thrown.

OK, great. This also promises that you can put any kind of data in any kind of view (not just a TextView). But I didn’t know what it meant by “if a SimpleCursorAdapter.ViewBinder is available.” Turns out it’s pretty simple:

  1. Implement the SimpleCursorAdapter.ViewBinder interface (it has only one method, setViewValue, which gives you the Cursor and the view to work with – and just return false to let the adapter’s default behavior handle the binding). I did this for LogCursorAdapter in an inner class.
  2. Instantiate your implementation and use setViewBinder on your SimpleCursorAdapter instance to set it up as the binder. This makes it “available” for the process described above.

This is arguably better than overriding setViewText because you wouldn’t even have to subclass the adapter to do it – or even create a class (it could be an anonymous implementation). And of course you can access all of the cursor columns in any way you please. Nice.

As far as my earlier data retrieval woes, this gave me the ability to pull data out the way I wanted. Sqlite seems to be storing plenty of precision in the NUMERIC column type; it was just a matter of it being retrieved as a String that caused truncation of precision. In this case the solution was just to pull it out as a Long or Double as appropriate and format it myself (I also learned about DecimalFormat which was very helpful).

Advertisements

Activity odds n ends, and eclipse platform source attach

It’s worth noting this bit of oddity about adding headers/footers to ListView and the ListView getting a wrapped adapter. On the other hand, he implements no empty view. I went back and looked at my reference app for a lot of how I want things to work, gtasks (wish the developer would open source it!). And wouldn’t ya know it – the text entry field that he has as the header? can’t be reached by navigation. I don’t feel so bad now :-) Though it’s still a nifty implementation I want to copy.

Another thought about having an “Add item” list header/footer – if it’s in the footer, the virtual keyboard will helpfully cover it up so the user can’t see what s/he is typing. So that’s a good argument for having it in the header. Though I think I’m gonna just try having a header element that acts like a list item and goes elsewhere to create a new item; I don’t think it’ll even require a custom adapter.

An interesting tidbit: Activity.finish() evidently is not a trap door like “exit” in C or bash; whatever method you’re in continues execution, and only when you complete whatever callback you’re in the middle of (since *everything* in an Activity is driven by some callback) does the Activity see that it’s time to leave and heads for the exits in an orderly fashion (first calling onPause() where you can helpfully check isFinishing() to perform any last-minute actions, then onStop, then onDestroy). I guess it seems obvious now that I think about it, but it was surprising at first.

Checking for isFinishing() in onPause() is probably a better way to handle the back key than onKeyDown() which gets called on every type of keypress.

I need to make a note to read this post on styles and themes more thoroughly. For now I’m just gonna use it to find out what I can put in textAppearance :-)

Ooh! A more coherent post about attaching platform source in Eclipse! It’s not trivial (which is good to know) but doesn’t look too bad. IRC folks helped me with that… damn IRC folks can be helpful! This was written quite some time ago, so I think it could use a little updating. Notably, you want to get a version of the repos that matches what you’re developing against. It’s not immediately evident which branch names to use – there doesn’t seem to be a logical scheme like “release-1.0, release 1.1, release 1.5…” When repo gets a project, it lists the branches and tags for it, which look something like this:
 * [new branch]      cupcake    -> korg/cupcake
 * [new branch]      cupcake-release -> korg/cupcake-release
 * [new branch]      donut      -> korg/donut
 * [new branch]      donut-release -> korg/donut-release
 * [new branch]      eclair     -> korg/eclair
 * [new branch]      master     -> korg/master
 * [new branch]      release-1.0 -> korg/release-1.0
 * [new tag]         android-1.0 -> android-1.0
 * [new tag]         android-1.5 -> android-1.5
 * [new tag]         android-1.5r2 -> android-1.5r2
 * [new tag]         android-1.5r3 -> android-1.5r3
 * [new tag]         android-1.5r4 -> android-1.5r4
 * [new tag]         android-1.6_r1 -> android-1.6_r1
 * [new tag]         android-1.6_r1.1 -> android-1.6_r1.1
 * [new tag]         android-1.6_r1.2 -> android-1.6_r1.2

…but often with a lot of clutter that varies from project to project. Now if you try to do repo -b with one of the tags (which seem logically named), it complains, probably because tags aren’t branches. Maybe there’s a flag to access those. But to get a branch, the way to go seems to be getting it by codename, i.e. cupcake, donut, eclair. Those are probably moving targets, while -release ones will be old. (You can’t win.)

And then when you’ve finally downloaded it, it needs moving around before you can even use it. And then there’s the poor guy saying his download never finishes. You know what? After I’ve got this figured out, maybe I’ll upload snapshots of zipped-up attachable code for each release to my free web hosting space for others to use without all the fuss :-)

complications with ListView and headers/footers

Well, of course it’s always the obvious thing you don’t try. There really was only one field in R.drawables – what I wanted was android.R.drawables! *facepalm* GridView is much more useful here than Gallery – not sure where Gallery would actually be preferable now that I look at it.

I ran into a little quirk. I set up a listview’s data by calling getListView().setAdapter(adapter), and later when I tried to retrieve the adapter with getListAdapter(), I got null. I dug into the platform source a little bit. It seems that ListActivity#setListAdapter sets the adapter both on the ListView and its own member mAdapter. ListActivity#getListAdapter is not simply a convenience method replacement for getListView().getAdapter() but actually gets the stored mAdapter in the ListActivity. Apparently the intention is that ListActivity#setListAdapter be called so that both places are set. But the storage in two places isn’t necessarily redundant – when ListView#setAdapter is called, if headers have been added to the ListView, the adapter is wrapped as new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter) and stored. I saw some reference to this before but didn’t understand it until I saw it for myself.

Here’s another interesting bit I found in the code for ListView. If you add a header with addHeaderView(v), it is assumed you want that view to receive focus / be selectable, because that’s how it’s implemented (though docs say nothing):
    public void addHeaderView(View v) {
        addHeaderView(v, null, true); // where “true” is for “isSelectable”
    }

I’m not sure why this would be the default, but it is what I’ve observed – headers act like list items (and footers work the same way). I think it’s out of consideration for those who are using buttons or trackballs to navigate and so need to be able to “get to” something to see it. This isn’t so great if your header/footer has views in it that need to receive focus/clicks, like form fields. The entire header/footer gets focus, not the views.

But actually that’s true even if the header/footer isn’t selectable – then the focus doesn’t go beyond the list items. Either way there doesn’t seem to be a way to navigate to the subviews, unless of course you have a touchscreen and can just touch them. That’s fine with me but I’m not sure it’s great for everyone else. Maybe there’s a way to work with the attributes to manually set up where focus goes.

And it gets better – if the list has no items in it and an empty view defined, the empty view is shown, and NO header or footer! This seems dumb to me. Maybe I’m just dense but I think the most useful thing to do with a header or footer is implement a button or input form to create a new list item, right? So you want to see “list has no items, create a new one here” but the way it works now, you would have to re-implement your header/footer in the empty view – even if you can <include> the relevant layout, you would need to wire up observers and data for it separately.

So to recap what I’ve found so far: if you want a list with headers/footers that scroll with the list and include focusable elements themselves, ListView has significant problems because there’s no way to navigate from the list items to the header/footer items. And if you specify an empty view, the header/footer won’t display.

This is the 1.5 API – I wonder if any of this works differently in later APIs? This seems like a common problem, and it seems easily fixable in the platform – just have headers and footers display with the empty view, and have the ends of the list continue focus to the header or footer views if the header/footer isn’t selectable. But this must be harder than it sounds or surely the main Android developers would have done it already.

Maybe I can create a custom ListView subclass that can accept XML attributes specifying header/footer elements and also automatically wires up where the focus goes for them.

RTFM

Going back to re-scan the Android dev guide now that I understand a bit better what’s going on. It makes a lot more sense now.

First I had a look at binding list views to data. This bit of code had some nuggets:

ArrayAdapter adapter = ArrayAdapter.createFromResource(
   
this, R.array.colors, android.R.layout.simple_spinner_item);
adapter
.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);

My code was similar, except the setDropDownViewResource call was commented out because the method was unavailable when I tried it; looking more closely I saw that I’d set adapter to type SpinnerAdapter – which is just an interface without the actual methods of an actual Adapter; wonder where I got that from? Correct typing allowed me to use the method. I had not noticed before that there were prefab views available for the spinner; oddly, however, using these resulted in invisible text and strange buttons on the dropdown:

(And, just now noticing that ScribeFire isn’t inserting an image intuitively. Bleh. Not that WordPress hasn’t given me “shaking fist syndrome” from trying to insert images too.)

Also discovered notifyDataSetChanged() which keeps me from having to create a new cursor and adapter when I know or suspect the data for the view has changed. I guess that’s one of the benefits of Activity cursor management? Just requires retrieval and careful casting of the adapter to call the method. Going to try it now. In my case, with a custom adapter (subclassing SimpleCursorAdapter), it looks like so for my ListActivity:
    ((EventCursorAdapter) getListAdapter()).notifyDataSetChanged();
Well, that does not in fact re-run the query and get the new data. So that’s a bit misleading. But it DOES seem to work for my spinner:
    ((SimpleCursorAdapter) mSpinner.getAdapter()).notifyDataSetChanged();
Hmm… what’s the difference? Stack Overflow has someone recommending futzing with the cursor directly on the ListActivity though the next response indicates notifyDataSetChanged should work. Comments on this ListActivity tutorial indicate some puzzlement about the issue as well. A response here indicates it should work.

It looks like SimpleCursorAdapter gets its notifyDataSetChanged method from BaseAdapter, which probably does nothing but update the view as no query logic could be involved. That doesn’t explain why there’s different behavior between a Spinner and ListActivity which both use SimpeCursorAdapters, but it suggests I can try overriding the method in my custom adapter and requerying the cursor.
    @Override
    public void notifyDataSetChanged() {
        getCursor().requery();
        super.notifyDataSetChanged();
    }

Ooh! Bad idea! Apparently the requery causes notifyDataSetChanged to be called and it recurses into a stack overflow. I can add a separate method to try the same thing, though. And… that works – and the cursor requery automatically kicks off the notify and redraw.

I must be missing something. I don’t understand why I’d need to explicitly requery in the case of an adapter for ListActivity and only need to notify for a Spinner.