In last two tutorials, we talked about binding “static” array list to List Views. In this tutorial, we are going to talk about the “dynamic” one, which in my opinion, one of the most important concept to work with in Android — Cursor.

Why Cursor is dynamic? Because cursor contents are not filled in the memory until you really need it, so, suppose you have a query that returns 1000 records, you don’t need the memory and processing overhead to cater that 1000 records, but maybe only 10 or 20 records which is currently displaying on screen. Consider the relatively small amount of memory (and even smaller allowed to your application), Cursor is a better alternative than “static” collection of objects. In Android-Binding, we are using CursorCollection (after v0.45, and original CursorObservable is deprecating) to help binding Cursors to Views.

Series Directory:

  1. Introduction to List Views Binding
  2. Custom Row Model for List View
  3. Binding to Cursors (this)
  4. Lazy Loading

The usage of CursorCollection is very similar to ArrayListObservable, we have simple constructor to tell what is the type of the View Model for the Cursor Items (we call it Row Model in Android-Binding):


public final CursorCollection<MusicItem> MusicList =
    new CursorCollection<MusicItem>(MusicItem.class);

Next, we need to define the Row Model (MusicItem). Here, the Row Model must be a sub-class of IRowModel (interface) or RowModel (abstract parent):


public static class MusicItem extends RowModel{
    public final IdField Id = new IdField(MediaStore.Audio.Media._ID);
    public final StringField Title = new StringField(MediaStore.Audio.Media.TITLE);
}

If you look at the code in MarkupDemo (ListViewWithCursorSource.java), MusicItem is an inner class of the View Model. Since CursorObservable may dynamically construct those RowModel, it must be declared as static class, with a parameter-less public constructor. If in case you really need it to be non-static inner class and/or parameter is needed, you need to provide a RowModelFactory to the CursorObservable, but this is far beyond our tutorial coverage.

The MusicItem class looks pretty much like your familiar ORM frameworks. Yes, Android-Binding will try to convert records of Cursor to Row Model Object. As in above example, we have two columns, Id and Title, which came from the following query:


Cursor c = activity.getContentResolver().query(
    MediaStore.Audio.Media.INTERNAL_CONTENT_URI,
    new String[]{MediaStore.Audio.Media._ID, MediaStore.Audio.Media.TITLE},
    null, null, null);
activity.startManagingCursor(c);
MusicList.setCursor(c);

We are querying Media Store for Audios, with Id and Title Columns only. Each time the Row is accessed, the two values will be filled into the RowModel automatically.

How it works

Cursor, as I mentioned before, is dynamic and it’s good to know how it works to understand its limits.

Since we should not get all the contents from Cursor and load them in memory, Android-Binding will try to keep those “needed” Rows in memory and those consider unused will be recycled. For CursorObservable, it will maintain a set of cache for those Row Models, once the particular row is accessed, it will:

  1. Look into cache to see if it exists, return if so
  2. No cache, create a new instance of that Row Model and fill in the content
  3. Remove those unneeded cache objects

*The word “needed” depends mostly to user interface, for ListView, by default it will be number of visible items * 2 (and you may customize the cache size and caching policy via the constructor of CursorCollection).

In a Row Model, you can define anything like other View Model do. You may have Dependents, Commands and if you change the internal state of it, changes will also reflects to UI… BUT, once the Row Model is removed from cache (e.g. you scroll forward two pages and back again), the changes will be reverted to original, since the data is coming from Cursor.

Updating content

It’s easy, however, to persist changes.


public class MusicRowModel extends CursorRowModel{
    public final IdField Id = new IdField("_id");
    public final StringField Title = new StringField("title");
    public final StringField Artist = new StringField("artist");
    public final FloatField Rating = new FloatField("rating");

    public Command Save = new Command(){
        public void Invoke(View view, Object... args) {
            Toast.makeText(getContext(), "Saving " + Title.get(), Toast.LENGTH_SHORT).show();
            mDb.updateEntry(Id.get(), Title.get(), Rating.get(), Artist.get());
            getCursor().requery();
        }
    };
}

NB: In above example, the Cursor is queried from local database, not Media Store.

We have the Save command, that put back the changes to database. After that, the Cursor must be requeried in order for the CursorCollection to update the state. Lastly, the save command can be wired to a Save Button, the onChange event for RatingBar etc.

Wrap ups

Cursor is essential to most Android application, but it is also very clumsy works to do to wire Cursor content to UI elements; thanks to Android-Binding this is much easier to work with and more intuitive. In the final part of this series, we are going to show how we can do Lazy Loading in Lists.

Advertisements