Welcome back to Exploring J2ME, my series introducing application development using J2ME and the MIDP. You should already know about J2ME’s Record Management System (RMS), which provides persistent storage for mobile applications, and how the MIDP implements this storage as a set of named flat files. If you don’t, I encourage you to check out the previous installments of Exploring J2ME, which I’ve listed for you below.
Get the back story
If you’re just joining us here, you’ll probably want to check out the earlier articles in this series:
"Exploring J2ME: Building an expense tracker"
"Exploring J2ME: Building an expense detail form"
“Exploring J2ME: Using the Record Management System”
When we last visited ExpensesApp, we finally had the ability to store expense items across application runs, through the RecordStore class, which provides rudimentary access to the J2ME record store. However, there was a minor problem in that the expense items were displayed in the lsMain list in the order they were entered, which made finding a particular item a little more difficult than it should be.
In this article, I’ll introduce the RMS’s record-sorting API—specifically, the RecordEnumeration class and RecordComparator interface, both of which you find in the javax.microedition.rms package. I’ll also touch on the RecordFilter interface, which allows you to search for particular records in a record store. You can download the latest source code for ExpensesApp here.
Sorting with RecordComparator
You can see in Listing A that ExpenseInfo.LoadExpenses has once been again modified, this time to make use of a RecordEnumeration object that can retrieve records from a record store in something other than their insertion order:
RecordEnumeration enu = rs.enumarateRecords(null, new ExpenseComparator, false);
The RecordStore.enumerateRecords accepts an instance of a class called ExpenseComparator, which implements the RecordComparator interface and is used by RecordEnumeration to determine the order in which records should be sorted. I’ve placed the code for ExpenseComparator in Listing B.
Check out the RecordComparator.compare method, where all the action takes place. The compare method is handed two records, each in the form of a byte array (the bytes and bytes1 array parameters), and must extract any data needed to determine which record should come first in RecordEnumeration’s sort order. The method then indicates the relative order of the two records like this:
- · If the record represented by bytes should come first, compare returns ExpenseComparator.PRECEDES, and bytes will appear in the enumeration before bytes1.
- · If the reverse is true, and bytes should come after bytes1, compare returns ExpenseComparator.FOLLOWS, causing bytes1 to appear in the enumeration before bytes.
- · If both records are equivalent (entered on the same day), compare returns ExpenseComparator.EQUIVALENT and the two records will appear in an arbitrary order in relation to each other.
In the case of ExpenseComparator, I extract the ExpenseDate field (which is saved to the record store in “number of milliseconds in the current epoch” format) from each of the two records I’m comparing and return the appropriate value based on which date precedes the other.
When implementing RecordComparator, remember that compare will be called at least once for each record in the record store when the enumeration is initially populated. So you should conduct your comparisons as quickly as possible to prevent unnecessary slowdowns in your application. Also remember that the two records you’re comparing will not necessarily be immediately adjacent to each other in the final, sorted enumeration.
You probably remember me griping a fair bit last time about how frustrating the RecordStore class was to use and how many of its methods were inconsistently named compared to other classes in the RMS API. Once again, let me stress that obtaining the JavaDoc documentation for the MIDP class API will serve you well when working with J2ME.
Looking again at Listing A, you’ll note that the record extraction loop now uses the RecordEnumeration.hasNextElement method as its control variable. As before, each expense item’s record ID is preserved in an instance of ExpenseInfo, and the data from each record is retrieved in order: expense date, description, dollar amount, cents amount, and finally category, through the use of two stream reader classes.
Using RecordEnumeration in combination with ExpenseComparator makes the order of expense items in lsMain a bit more logical, sorting them by date instead of insertion order. However, even if you’re not planning to sort records, you should consider using RecordEnumeration to retrieve records from a record store. Doing so is just easier than using the RecordStore class itself and also sidesteps a few potential problems, like the record deletion bug I discussed at the end of my last article.
Tricky record pointers
Inconsistent method names are just an annoyance, once you know to look out for them, but here’s a bigger problem that might cause you some lost sleep. You might think, after casually glancing over the documentation, that only the nextRecord and previousRecord methods move the record pointer in the underlying enumeration. Dig a little deeper; you’ll see that things work differently and that another method advances the record pointer as well: nextRecordId.
You need to be aware of this fact because you’ll almost always need to know a record’s ID to update it later. So the following approach to retrieving records would seem to make sense:
- 1. Call nextRecordId to retrieve the record ID of the next record in the enumeration.
- 2. Retrieve the next record using nextRecord.
- 3. Load the data from the record.
- 4. Repeat until there are no more records.
The trouble with that approach is this: Because calling nextRecordId also causes the pointer to advance, you’ll wind up retrieving only half of your saved records and you’ll catch a mysterious InvalidRecordIdException, to boot.
The solution to the first problem, as you can see in Listing A, is to use RecordStore.getRecord instead of RecordEnumeration.nextRecord to retrieve the record. I know, it feels kludgy; but that’s the way it works. The mysterious exception is thrown by nextRecordId in response to being called when the record pointer is on the final record in the enumeration, so be sure to write your code to avoid that situation.
Finding records with RecordFilter
Although I haven’t implemented it in ExpensesApp, it’s possible to use RecordEnumeration to search for records as well. To do so, you pass an instance of a class implementing the RecordFilter interface to RecordStore.enumerateRecords and omit the RecordComparator entirely. RecordFilter has a single method, matches, that receives a single byte array parameter representing a record. This method is responsible for examining the record and returning true or false based on whether the record matches the desired criteria.
For example, suppose I have an implementation of RecordFilter called ExpenseFilter, shown in Listing C, which searches for all expense item records with a category of ExpenseInfo.CATEGORYMEALS. To retrieve an enumeration containing only those records, I could use something like the following code:
RecordStore rs = RecordStore.openRecordStore(RS_NAME);
RecordEnumeration enu = es.enumerateRecords(
new ExpenseFilter, null, false);
At this point, the enu variable would contain only records for expense items that had been assigned the category of meals in the ExpensesApp UI.
We’ve still got some work to do
So far, ExpensesApp has shaped up pretty well. The application now has a decent UI, with a shortcut for adding new expense items, and it's finally able to be of some practical use by saving information across runs. There still are some problems, though:
- · New expense items added to the list don’t appear in the correct place in the sort order.
- · Changes made to an ExpenseInfo instance are updated in memory regardless of whether the instance is saved to the record store—something I’m surprised no one has written in to point out.
- · The application isn’t a very good mobile device citizen; it doesn’t attempt to free any resources when it’s paused.
In the next article in this series, I’ll address these shortcomings by showing you how to use a “live” RecordEnumeration and the record change notification API.