Technology

Table Joins/Unions and ContentProviders: Reminderer


Ever since I wrote the new ContentProvider framework, Reminderer had this annoying bug where it wouldn’t auto-refresh the open tasks list whenever you added a new task. You added a task but the darn task wouldn’t show on the list until you rotated the phone.

The first approach was to force reload the Loader every time the fragment gets focus. While this hack works, its just that—a hack. This approach quickly becomes inefficient once the task list gets unduly large (as in hundreds of tasks). The second approach was to use a ContentObserver to listen for changes in the database. Fortunately, for some reason, as I’ll explain shortly, the content observer wouldn’t listen to changes in the table.

Continue reading

Technology

End of Phase 1: Reminderer


first screenshot

I finally finished the first draft of the alarm system! Yay. Screenshot on the right.

“Wait,” you might be saying to yourself, “That app looks like crap.” And you’re right. I’ve been focusing strictly on the back end. With that out of the way, I can now dog food the app. Normally, I work from the outside in—create a UI first then add backend functionality as needed. In this case, the alarm system is so straightforward (yet interconnected) that it made more sense to complete it first.

What’s next?

The immediate next step is to add folders or categories. Each alarm can belong to one folder/category. Each folder/category has its own view. You’ll also be able to add an unlimited number of subtasks to a task, with unlimited hierarchy. Eventually there will also be tags to put tasks in multiple categories.

I’m also going to start surveying similar apps.

Technology

Roboelectric and ContentResolvers/ContentProviders


Setting up Roboelectric is dead simple. However, you may find that code like the one shown below doesn’t work:

//save using a task provider
Button saveButton = (Button) activity.findViewById(id.save_button);
saveButton.performClick();

//check that task was saved
ContentResolver resolver = activity.getContentResolver();
Cursor cursor = resolver.query(//...

//fails!
assertTrue(cursor != null);

Nothing gets saved to the mock in-memory database. Your break points in the ContentProvider don’t get called.

What gives?

Register the ContentProvider

In Roboelectric, you need to manually register your content provider. The code below does the trick:

//instantiate the ContentProvider directly
TaskProvider taskProvider = new TaskProvider();
taskProvider.onCreate();

//register the ContentProvider
ShadowContentResolver.registerProvider(TaskProvider.AUTHORITY_NAME, taskProvider);

Thanks to Vardhan for the tip!

Unimplemented Methods

We’re almost there. The default mock in-memory database is pretty cool. For basic CRUD, it works pretty much like a real database except that everything is stored in memory.

However, you might find that queries don’t work. The reason is that as of 7/27/2013, Android API 16 (which is the default version Roboelectric uses) adds a new database method that Roboelectric hasn’t implemented yet.

The solution is to…implement the missing method yourself. You have two options:

  • Add the missing method directly to shadow class. (Requires that you work directly with the Roboelectric source code).
  • Create a custom class in your test and bind it at runtime to the framework. (I haven’t figure out how to do that yet in Roboelectric 2.)

The offending class is ShadowSqlLiteDatabase. Add this method and you should be good:

@Implementation
public Cursor rawQueryWithFactory (SQLiteDatabase.CursorFactory cursorFactory,
                                 String sql,
                                 String[] selectionArgs,
                                 String editTable,
                                 CancellationSignal cancellationSignal)
{
  return rawQueryWithFactory(cursorFactory,
                             sql,
                             selectionArgs,
                             editTable);
}
Technology

Reminderer: Database Backend Redux


I spent last week rewriting the database backend and boy was it a convoluted mess.

First, I tried salvaging the existing framework but I couldn’t figure out what the heck it was doing. Then I tried an implementation using ThreadPools, but handling configuration changes meant re-inventing the wheel. (Re-inventing the wheels means that there’s a good chance the code might break in future Android versions.) Then I tried a version using DAOs. Unfortunately, the “Android way” (using Cursors) gives better performance, so I started again from scratch.

But what is the Android way? and what did I really want?

Continue reading

Technology

Reminderer: Asynchronous Database Operations (Part 2)


I’ve been reading Predictably Irrational, a book on how we tend to make stupid (irrational) decisions. In particular, we have a habit of keeping as many doors open as possible, even if doing so is to our disadvantage. So what the heck does that have to do with asynchronous database operations? Keep reading.

In the previous post, we created a grammar for parsing tasks. Now we need a database to store the tasks. In Android, this is pretty straightforward—we decided to hide the database behind a ContentProvider. (That way if we decide to open up the database to other apps in the future, we’re more than half way there.) Creating a database and a content provider in Android is  well-documented. (You can also check out the classes in the com.frankandrobot.reminderer.database package at github.)

So far so good—straightforward and simple. Then it came time to integrate the database operations (CRUD) with the UI.

The problem is that the database operations can take a relatively long time and make the app appear sluggish. The worst case scenario is that we get the dreaded ANR dialog. We need to do the CRUD in a separate thread. Android gives you several options to do this. Among them are:

Options. So many options. And I wanted more!

When I got to this point, I was worried that the AsyncQueryHandler (being a handler) would get stopped or paused when the activity is stopped or paused. Instead of creating a simple test case to prove or disprove my theory, I proceeded to devour the stock Calendar app source code. Two weeks later, I found that it uses an IntentService in conjunction with a handler to do (some) of the CRUD.

The jury is still out on this handler-intent-service combination. But I can tell you this much—an AsyncQueryHandler does not get killed or paused when the activity gets killed or paused. (I had misunderstood the handler-activity relationship. Apparently, a handler’s life cycle isn’t as dependent on the activity’s life cycle as I thought.) So an extra two weeks later, the Reminderer app now has a working database backend. On the plus side, I am now wiser 🙂

The moral of the story is that we need to find a balance between accumulating knowledge and sticking to a schedule.

Check out the progress of the Reminderer app over at github.