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).

Exploring Android ListView

Some time ago I summarized some things I’d discovered about headers and footers on ListView in Android. Oddly enough, despite the dearth of real content there, it is consistently one of the top-viewed pages on this rather esoteric and scatter-brained blog. I think the simple reason is this: ListView is frustratingly obtuse if you try to do anything that’s not explicitly described in the docs, and because everyone needs to use Listview to display lists of information, everyone is looking for solutions to fairly common usage problems.

Recently I’ve had reason to revisit ListView’s oddities with a vengeance. I created some example code I could play with. I think this may be useful to others, so I created a github repo to share it – it will evolve as I try things out / figure things out. Let me know if it’s useful to you or if you have any better ideas for how to accomplish what I’m attempting.

Continue reading

Restricting text to one line with EditText/TextView

I’ve let slide my habit of writing about what I’m doing. Let’s try to pick it back up.

I spent some time style-izing some things in WhenDidI. For one thing, I wanted to restrict text fields to just one line (not allowing newlines). It’s not enough to have android:lines=”1″ on the EditText. That keeps the display to one line, but doesn’t keep the user from using newlines to create a multi-line entry. This was worth creating a style for:

    <style name=”NameEditor” parent=”@android:style/Widget.EditText”>
      <item name=”android:layout_height”>wrap_content</item>
      <item name=”android:layout_width”>fill_parent</item>
      <item name=”android:lines”>1</item>
      <item name=”android:inputType”>textCapSentences|textAutoCorrect|textAutoComplete</item>
    </style>

The inputType attribute allows many different values, so check them all out. Somewhere in there is the one that lets the on-screen keyboard suggest words while you’re typing. Anyway, the point here is – if you specify inputType but don’t include textMultiLine, the user doesn’t get to use newlines.
N.B. it can be nice to put layout_height and layout_width in a style to reduce clutter – of course this isn’t a good idea all the time.
So the user can’t enter multiline text now, but they can still enter giant amounts of text, and I’m wanting these values to display in one line in a ListView item. How do you cut text short in display? Well, there’s the android:ellipsize attribute, but this doesn’t actually seem to work. That bug report contains a workaround, however, without even using any deprecated attributes:
    <style name=”EllipseText” parent=”@android:style/Widget.TextView”>
      <item name=”android:lines”>1</item>
      <item name=”android:scrollHorizontally”>true</item>
      <item name=”android:ellipsize”>end</item>
   </style>

This does the trick, although in my scrolling ListView the ellipses seem to only show two dots instead of three. Maybe judicious use of padding / margins will fix that.

filtering listviews like the contacts app

I thought it would be nice to provide filtering capabilities in my ListViews – they could get long. It would be nice if this was kind of done automatically for you when you put android:textFilterEnabled=”true” on the ListView, but this is not the case; I suppose in the end it’s better to have to be explicit about exactly what’s being filtered and how, and this is Java after all.

Enabling a filter as above just enables the view to gather text the user types; it doesn’t actually do anything with it unless I set up the adapter to use it. I got a pointer from this thread. My ListAdapter is a subclass of SimpleCursorAdapter so all I had to do in my ListActivity is:

  adapter.setFilterQueryProvider(this);
  // implement FilterQueryProvider
  public Cursor runQuery(CharSequence constraint) {
        Cursor cur = mDba.fetchTrackers(mCurrentGroupId, constraint.toString());
        startManagingCursor(cur);
        return cur;
  }

I’m really hoping this doesn’t leak cursors or something. Anyway, having properly implemented fetchTrackers such that it returns a filtered query, that’s all there is to it – typing next now filters the list items.

Next problem: what happens when I type in a filter, select an item to start another activity, and then return? In the Contacts app (my model for this), the filter actually remains in place, which is a little odd. In my app, the filter *text* remains, but the items in the list are not filtered unless I start typing again! Very odd. I would like to either clear the filter or have the persistent filter applied when I resume the Activity.

There’s pretty much zippo on d.android.com about this. A Google search found me this interesting tidbit – not actually relevant to my problem I don’t think but it gives me a lead on something else I discovered the other day: if I used an alternate IME (say the chinese one) on a textfield, the text entered didn’t seem to get through to the OnKeyListener I set on the EditText. I’ll have to try a TextWatcher.

So anyway, my best bet is probably looking through the Contacts app – open source, right? Yay. Except I sometimes wonder if making something open source leaves less incentive to provide decent documentation. In any case, a quick look through the Contacts app didn’t yield any clues and I’m gonna have to stop for the night.

Movable list items?

Has anyone ever seen an Android app where the user can rearrange the order of items in a list? If they’re using a ListView I expect this is difficult; there’s no way to do drag. The only thing I can think of that’s even remotely like this is what the home screen does with a long-press grab and drag. Looking for a good design pattern to follow with a scrolling list.

Android: Summary of ListView header/footer findings

Note: This page gets a surprising percentage of traffic. If the subject interests you, you may find better answers at my later entry where I develop an app to try various layouts with ListView.

The issues

  • Scrolling with the list
  • Visibility
  • Ability to navigate without touchscreen (Dpad, trackball)
  • Ability to include interactive elements (text entry, buttons)
  • Doing with code vs. inside resources

Strategies

Strategies for headers and footers on ListView and pros/cons:

Static header: Element in your layout that precedes the ListView. Doesn’t scroll with list, so it takes up space. But is always visible and you can navigate to it and use interactive elements. Also can be done inside resources.

List header: Element added to the ListView with addHeaderView. Scrolls with the list, but is invisible if an empty view is being shown. Can be navigated to but interactive elements on it can’t be. Can’t be done within resources, currently must be attached within code.

Empty view: Standard ListView layout feature.  Only visible when the list is empty, and can’t be a reuse of another view in the activity (so any interactive elements have to be wired up separately from e.g. a header). Interactive elements can’t be navigated.

List footer: Element added to the ListView with addFooterView. Scrolls with the list and is visible even with an empty view. Can be navigated to but interactive elements on it can’t be, and the virtual keyboard is likely to cover the view if the list has even a few items. Can’t be done within resources, currently must be attached within code.

Static footer: Element in your layout that comes after the ListView. This is a non-starter because once your list is as long as the screen, this element can’t be scrolled to at all.

My conclusion

If you’re trying to have a nice interactive element in your list for adding list items, probably the best bet is to make it a non-interactive list header or footer that opens a dialog on click or a context menu on long-click. If you really want to avoid popping things up and want to have interactive elements, then go ahead and put them in a list header, as most everyone will be using the touch screen anyway, just be aware of the navigational issues.

preferences, more headers/footers on listview

Java tutorial entries here and here helped clear up my understanding of generics and what’s going on with my last post. Basically what I was trying to do with an array is not supposed to be done, for esoteric type-safety reasons. I tried refactoring to use List<Class<Activity>> instead of an array but the cascading errors/warnings I got from that were puzzling. Maybe try that again later.

Annoyance: my preferences class has to use PreferenceManager.getDefaultSharedPreferences(context) all over its static methods. Surely there’s a way to cache the result of this verbosity? Probably in an Application object. I tried creating an Application object and caching getSharedPreferences() during its constructor (so that it could go in a final member) but this gave me a null pointer error:
  java.lang.NullPointerException
     at android.content.ContextWrapper.getSharedPreferences(ContextWrapper.java:134)

Looks like the line it’s talking about is this:
     return mBase.getSharedPreferences(name, mode);
but mBase is set in the constructor so I don’t know how that would be null. I don’t know, but I did find out that if I called it in onCreate in the application, that worked fine. So I guess something is in place there that isn’t during the constructor.

I futzed a little bit more with setting headers/footers on the ListView. Here’s what I recall finding so far:

  • Forget about putting buttons or editors or anything like that which receives focus in headers/footers; that’s not going to work with navigation without some major magic (though they’ll be accessible by touch)
  • If I have header and an “empty” view, the header won’t show when the empty view is shown (list empty). I think the footer does, strangely. If I don’t define an empty view, headers will be shown all the time.
  • I can put stuff in my layout after the listview, and it looks kind of like a footer. But it can’t be navigated to, and more importantly if the list is longer than the screen it can’t even be seen.

I’m planning to just define adapters and views for custom list elements to do what I want (e.g. a “create a new entry” list item, no need for empty view), and otherwise use menus.