POJO's versus Cursors in Android
Asked Answered
E

4

34

I usually tend to define the model layer of my apps using POJO's, such as Article, Comment, etc.

I was about to implement an AlphabetIndexer in the adapter of one of my ListViews. Right now this adapter accepts a Collection of Articles, which I normally get from my wrapper around an SQLiteDatabase.

The signature of the AlphabetIndexer constructer is as follows:

public AlphabetIndexer (Cursor cursor, int sortedColumnIndex, CharSequence alphabet)

Since this doesn't accept a Collection or something similar, just a Cursor, it got me wondering: maybe I shouldn't be creating objects for my model, and just use the Cursors returned from the database?

So the question is, I guess: what should I do, represent data with Collections of POJO's, or just work with Cursors throughout my app?

Any input?

Entwistle answered 30/3, 2010 at 12:34 Comment(2)
Would you like to use Cursors provided by a lib? See: #4285898Salomone
I am doing something similiar HERE #10224733Festatus
I
13

I have run into similar issues. Right now, I am tending away from POJOs. Note, though, that you can create your own Cursor interface for a collection of POJOs, if you so choose.

Imperturbable answered 30/3, 2010 at 13:33 Comment(3)
I know about that, but it seems a bit circular... creating a Cursor for a Collection that was created from a Cursor. :)Entwistle
Oh, no question. That's one of the reasons I am tending away from POJOs and leaving things just in Cursors. In MVC terms, you wind up with dumb models and smart controllers.Imperturbable
I decided to accept this as the answer, since I now use this approach. Specifically, I created a public inner class in my SQLiteOpenHelper for each table it has. Those classes contain a bunch of methods to prevent me from having to mess with column names or numbers. The performance difference is immediately obvious in ListViews with as little as a few dozen entries. Also, I seem to prefer CursorAdapters to BaseAdapters. I got the inspiration for this from your books (specifically page 102 in Android Tutorials 2.9), which I didn't have back when I asked this question, so thanks! :-)Entwistle
P
12

I like to create Cursor-backed POJO classes. A Cursor-backed POJO class has a constructor that takes a Cursor and provides the following benefits:

  • Easy to use getters that return the proper content type, much better than getting indexes and having to remember the type of data in the database
  • Getter methods that compute their results from other getters, just like how OO programming ought to be
  • Getter return values can be enums!

These few benefits are well worth some boilerplate code, many many bugs have been averted now that user-engineers aren't accessing cursor columns themselves. We still use the CursorAdapter class, but the first line in the bindView method is to create the Cursor-backed POJO from the Cursor and from then on the code is beautiful.

Below is an example implementation, it's a snap for user-engineers to turn an opaque cursor into clearly defined User object, from that point on it can be passed around and accessed just like a regular POJO so long as the backing cursor is not closed. The SmartUserCursor is a special class I wrote to ensure that the cursor position is remembered and restored before the cursor is accessed and it also stores the cursor column indexes so lookups are fast.

EXAMPLE:

public class User {

    private final SmartUserCursor mCursor;

    public User(SmartUserCursor cursor, int position) {
        mCursor = new SmartUserCursor(cursor, position);
    }

    public long getUserId() {
        return mCursor.getLong(SmartUserCursor.Columns.userId);
    }

    public UserType getType() {
        return UserType.valueOf(mCursor.getString(SmartUserCursor.Columns.type));
    }

    public String getFirstName() {
        return mCursor.getString(SmartUserCursor.Columns.firstName);
    }

    public String getLastName() {
        return mCursor.getString(SmartUserCursor.Columns.lastName);
    }

    public final String getFullName() {
        return getFirstName() + " " + getLastName();
    }

    public static User newUserFromAdapter(BaseAdapter adapter, int position) {
        return new User((SmartUserCursor)adapter.getItem(position), position);
    }

    public static User newUserBlocking(Context context, long UserId) {
        Cursor cursor = context.getContentResolver().query(
                Users.CONTENT_URI_CLIENT,
                Users.DEFAULT_USER_PROJECTION,
                Users.Columns.USER_ID+"=?",
                new String[] {String.valueOf(UserId)},
                null
        );

        if (cursor == null || !cursor.moveToFirst()) {
            throw new RuntimeException("No User with id " + UserId + " exists");
        }

        return new User(new SmartUserCursor(cursor, Users.DEFAULT_USER_PROJECTION), -1);
    }

    public final void closeBackingCursor() {
        mCursor.close();
    }

}
Posthumous answered 27/6, 2012 at 23:32 Comment(7)
any example code? Also is it slower since you have to load all the data from the cursors into the model objects?Circumbendibus
I don't load all the data from the cursor. I just have a bunch of getters that access the cursor internally. I will post an example later.Posthumous
cool thanks, looking forward to example, i currently use greendao which i think does something similar but would be cool to see an example of how this worksCircumbendibus
cool thx for example, can we see the SmartUserCursor too or is that for a client? Also when would you close the backing cursor?Circumbendibus
I can't give out the code for the SmartUserCursor, but it is just a helper class and it isn't strictly necessary for this example. It is still up to the user engineer to know when to close the cursor.Posthumous
Is SmartUserCursor extending Cursor or is it just a wrapper where you re-implement those functions like "getString()"?Ascham
SmartUserCursor is class that reimplements functions like getString to take enum values. The main purpose of the SmartUserCursor is to not have to lookup the column index all the time, it looks it up once internally and remembers it. It is a performance optimization.Posthumous
L
9

One vote for entity objects (POJOs). Passing cursors around, especially to the UI layer, feels so wrong to me (whether or not the Android sdk kinda implies doing it that way). There are usually several ways to populate your UI, and I tend to avoid those that directly use cursors. For example, to populate my custom list views, I use a SimpleAdapter and give my collection objects the ability to return a representation of themselves as a List<? extends Map<String, ?>> for the SimpleAdapter's constructor.

I use a pattern where each table is wrapped by an entity object and has a provider class that handles my CRUD operations associated with that entity. Optionally if I need extended functionality for the collections, I wrap them too (ie. EntityItems extends ArrayList<EntityItem>) The provider has a base class that I pass a reference to a DbAdapter class that does the heavy lifting around the db.

The biggest reason, other than personal preference, is that I want to hide this kind of code as far away from my UI as possible:

String something = cursor.getString(cursor.getColumnIndex(COLUMN_NAME_CONSTANT));

If I see that kind of code inline in the UI layer, I usually expect to see much worse lurking around the corner. Maybe I've just spent too much time in the corporate world working on big teams, but I favor readability unless there's a legit performance concern or if it's a small enough task where the expressiveness is just enterprisey overkill.

Lubricate answered 30/3, 2010 at 14:11 Comment(3)
"I use a SimpleAdapter and give my collection objects the ability to return a representation of themselves as a List<? extends Map<String, ?>> for the SimpleAdapter's constructor" -- that involves a lot of data copying, chewing up CPU time and generating garbage. That may or may not matter for any given app.Imperturbable
I keep a close eye on things like that, and I cache the results of these kinds of calls so I'd hardly think that my use of a convenience function like this would incur a significant memory or cpu perf hit. This brings up question that maybe you can answer though. If I populate a HashMap and all my keys are initialized string vars and all my values are string properties of initialized objects, by calling map.put(key, value) aren't I just passing around pointers as opposed to creating a ton of new objects (garbage)? I would assume so, by I don't know Java well enough to answer that for myself.Lubricate
Hey Rich. I also like the approach of having the UI agnostic to where the data comes from, and cursors code in the UI looks ugly to me too, but I can find a couple of reasons why they are needed. First, they are the most efficient way of linking data to things like ListViews. Second, to convert to a collection you have to iterate over your cursor linearly, O(n), create one object per row that you wouldn't in the cursor, and the GC will have to collect those at some point, but most importantly, you are loading your entire collection in memory. Cursors only hold the current row.Arrington
R
2

Answers are 4 years old. I think now we have enought CPU power to get away with more stuff. My idea would be to work only with POJOs and ArrayLists; and extending CursorLoader to map cursor to POJOs in the background and deliver arraylist to activity;

unless youre queries hundreds of rows but then, how often are you doing that vs. niceness of using POJOs, getters and setters

Road answered 2/4, 2014 at 22:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.