In previous articles in this series, we've introduced J2ME’s UI controls, persistent storage, and record-sorting APIs as we developed our sample application, ExpensesApp. In this article, we’ll polish up ExpensesApp a bit by reducing its memory requirements and correcting some data problems using the record change notification API. Go ahead and download the latest source code for ExpensesApp, and let’s get started.
Looking for something?
You can get caught up on this series by reading the previous installments:
- · "Exploring J2ME: Building an expense tracker"
- · "Exploring J2ME: Building an expense detail form"
- · "Exploring J2ME: Using the Record Management System"
- · "Exploring J2ME: Sorting records"
Lowering memory requirements
Earlier versions of ExpensesApp used a vector to store the instances of ExpenseInfo representing the records saved in the application’s data store. Although this was a fairly simple choice, as managed collections, vectors are fairly consumptive of resources. That’s not a good thing in a limited memory environment, such as a mobile device.
As you can see from the ExpenseInfo.LoadExpenses method in Listing A, the Expenses MIDlet now uses an object array to hold database records in memory. By making this change, I was able to lower the memory usage of a typical run of ExpensesApp (start the application and add a new expense) by almost 10 KB. That may not sound like much, but when you are dealing with a platform that measures the total amount of available memory in tens of kilobytes, it’s a significant gain. Also, I rewrote the method to reuse the two stream reader objects rather than re-create them during each loop iteration, as was done before.
Expenses also didn’t share resources very well. It didn’t respond to the pauseApp event, which is fired in response to a user switching to another application, by freeing any memory. That’s bad mobile device citizenship of the worst kind. The application now handles pauseApp a bit more considerately by freeing the memory used by the expenseItems array. That change forced some modifications in other areas as well: The startApp event handler had to be modified to rebuild the array if necessary (Listing B). You’ll recall from previous articles that startApp fires not only when a MIDlet is initially started but also when it transitions from paused to active states. We could free some additional memory by removing the UI objects in pauseApp, if necessary.
Solving synchronization issues
In addition to the memory usage issues, previous versions of ExpensesApp had a few data synchronization problems. You may remember these from last time, but I’ve listed them below in case you can’t—hey, it has been a while:
- · New expense items weren’t automatically added to the lsMain list in the correct sort order.
- · Deleted expense items were still represented in the vector and thus remained in memory and in the lsMain list. I solved this problem in the previous article by rebuilding the vector each time the cmDelete command was invoked, an expensive operation. I also used a somewhat kludgy way of determining when a new record was added to the data store, which could conceivably have resulted in some unnecessary vector re-creations.
- · Changes made to expense items were reflected in memory regardless of whether they were committed to the record store.
These data synchronization problems are easily solved by making use of the RMS’s record change notification system, which is embodied by the RecordListener interface. Let’s take a closer look at this interface.
Record change notifications with RecordListener
The main Expenses class now implements a new interface, RecordListener, which provides notifications of changes in the application’s record store. The RecordListener interface has three methods, all of which accept an argument representing the record store and the ID number of the affected record:
- · RecordAdded is fired whenever a new record is added to the record store using RecordStore.addRecord.
- · RecordDeleted is fired whenever a record is deleted from the record store by a call to RecordStore.deleteRecord.
- · RecordChanged is fired whenever a record is updated using RecordStore.updateRecord.
You set up a RecordListener by passing an instance of an implementing class to the addRecordListener method of RecordStore. As you might expect, multiple RecordListeners can be added for a single RecordStore.
ExpensesApp handles each of these listener events in essentially the same fashion, as you can see from Listing C. The addition and deletion event handlers clear the lsMain list, then rebuild the expenseItems array, and finally rebuild lsMain from scratch to reflect any changes. The changed event handler, on the other hand, simply rebuilds the expenseItems array, since an updated record won’t affect the items displayed in lsMain. Also, note that because I don’t care about anything beyond the fact that the event occurred, I don’t do anything with the RecordStore and ID number arguments that each event receives.
In addition to providing an easy method for keeping a MIDlet synchronized with its local data store, the RecordListener API really comes into its own when dealing with applications that independently update themselves with a remote server database. I’ll show you how that’s accomplished with the Generic Connection Network in the final installment of the Exploring J2ME series, coming soon to a development Web site near you.