| Sign In/My Account | View Cart |
The first part of this article showed how to use the HTTP support in a MIDP device to connect to Amazon's online bookstore and fetch details for a book, given its ISBN. The choice of ISBN as the key was based on the fact that the Amazon Web server provides a query that returns the details for a book given its ISBN, whereas searching by other means (such as author name or book title) sometimes results in an intermediate page being delivered, buried within which is a link to the page that the application needs.
From the user's point of view, however, ISBN is about the worst choice that could have been made. How many ISBNs do you carry around in your head? Even the most zealous of authors, I'm prepared to bet, don't memorize the ISBNs of all (or even any) of their books. To make the user's life easier, the second part of this article creates a local bookstore for the user. The local bookstore retains the details of all books previously obtained with the ISBN application, so the user can locally look up books by title. The application then gets the ISBN from the stored book details and uses the ISBN query to download the latest information from Amazon. In addition, the bookstore retains the sales ranking and the number of reviews, so that they can be presented from the local cache without having to make a network connection. You can also compare the old (local) figures with the latest data fetched from Amazon.
The bookstore is implemented as a new MIDlet, called
PersistentRankingMIDlet, which is in the same MIDlet suite as the MIDlet shown in the first part of this article. When this MIDlet executes for the first time, it prompts you to enter an ISBN and then fetches its title, sales ranking, and number of reviews, just as the first MIDlet did. It then stores these details on the device so that the next time you start the MIDlet, you will see a list of the books that you have already made queries for, as shown in the leftmost screenshot of Figure 1. This list is presented in alphabetical order, sorted by the book's title.
|
|
When you select a book from the list, the most recently obtained details are displayed, along with a button called Menu that allows you to access a menu of possible operations, shown in Figure 2.
|
|
In this menu, the New operation lets
you enter a new ISBN, and is therefore equivalent to the functionality provided by the RankingMIDlet developed in the first part of this article. The
Delete option removes a book from the bookstore. Finally, the
Details menu item causes the application to connect to Amazon.com, refresh the book details, and present a screen showing the difference between the new and old sales rankings and number of reviews. Figure 3 shows that the book has a ranking of 7,303, up 528 from the last time the device queried for information on the book.
|
|
The extra facilities provided by this MIDlet require the ability to store book
details on the MIDP device. All MIDP devices will provide some kind of long-term storage that is guaranteed to be preserved, at least while the device has some kind of power applied to it, which in the worst case means as long as the battery powering the device is not allowed to become completely discharged. Different device types provide different types of storage, so the MIDP profile defines a device-independent programming interface that allows a MIDlet to use whatever storage is available without needing to be aware of how it is actually implemented on the device. The API for this
record management system is provided in the javax.microedition.rms
package, a complete description of which, along with annotated reference information, can be found in O'Reilly's J2ME in a Nutshell.
The MIDP record-management APIs are based around the RecordStore class, which represents a collection of related records. Each RecordStore on a device is created and managed independently and belongs exclusively to the MIDlet suite containing the MIDlet that created it. All MIDlets in this suite can read and write records in any of their shared RecordStores and can delete the individual
RecordStores if required. On the other hand, for security reasons, MIDlets cannot access or even find out about the existence of RecordStores belonging to other MIDlet
suites. The tight binding between a MIDlet suite and its RecordStore is also apparent in a couple of other ways:
A RecordStore has a name composed of up to 32 Unicode characters. This
name is case-sensitive and must be unique within the MIDlet suite. However, the names used
by one suite can overlap those used by a different suite, because the MIDlet suite is
implicitly part of the platform-dependent key used to identify the underlying resources
in which the RecordStore is implemented.
When a MIDlet suite is removed, any RecordStores that any of its
MIDlets have created are automatically deleted at the same time. Since MIDlets can only
be installed or removed as suites, the issue of what should happen to a
RecordStore created and used by one MIDlet in a suite does not arise.
For the purposes of the bookstore client, we create a class called
BookStore that provides a higher-level interface, which allows the application to work with the data for an individual book rather than the
records that the RecordStore class deals with. As you'll
see later, those records are a very primitive concept. The BookStore class maps
directly to a RecordStore called, appropriately enough, BookStore,
which is automatically created or opened as required.
The static openRecordStore() method opens a
RecordStore given its name:
public static RecordStore openRecordStore(String name, boolean create)
If a RecordStore with the given name exists, it is opened, and an
appropriate RecordStore object is returned. If it does not exist
and the create argument is true, then an empty store
is created. If create is false, this method
throws a RecordStoreNotFoundException, one of several exceptions
derived from the base class RecordStoreException that
can be thrown by methods in the classes of the javax.microedition.rms.
The BookStore class opens or creates its associated RecordStore when its constructor is executed:
public class BookStore implements RecordComparator, RecordFilter {
// The name of the record store used to hold books
private static final String STORE_NAME = "BookStore";
// The record store itself
private RecordStore store;
// Creates a bookstore and opens it
public BookStore() {
try {
store = RecordStore.openRecordStore(STORE_NAME, true);
} catch (RecordStoreException ex) {
// Error handling not shown
}
}
A RecordStore can be opened several times by a single MIDlet and can
also be open for access simultaneously by more than one MIDlet (in the same suite,
of course). The RecordStore implementation keeps track of the number of
times that a given store has been opened. This count is decremented when the
RecordStore's closeRecordStore() method is called; the
underlying storage is closed (if this concept exists) only when the RecordStore
has been closed as many times as it was opened. This means that each invocation of
openRecordStore() must be balanced by a corresponding call to
closeRecordStore().
The RecordStore class has several global operations that operate at the
record store level, including:
public static String[] listRecordStores()RecordStores.
For security reasons, only the names of those RecordStores created by the
MIDlet suite containing the calling MIDlet appear in this array.public static void deleteRecordStore(String name)RecordStore cannot be deleted while
it is open.public void addRecordListener(RecordListener l)RecordStore
changes. The RecordListener interface defines methods that are called when
records in the RecordStore are added, removed, or updated.public void removeRecordListener(RecordListener l)addRecordListener().public int getSize()RecordStore, which
includes any private storage management information required by the implementation.public int getSizeAvailable()RecordStore can grow. Since the
implementation requires some space for private management information, the number of bytes
available for actual MIDlet data will usually be less than the value returned by
this method.public long getLastModified()RecordStore was last changed, in
the same form as the value returned by the System method currentTimeMillis().public int getVersion()RecordStore. This value is changed
whenever the content of the RecordStore is changed in any way. Checking
this value is a quick way to determine whether the store has been modified.public int getNumRecords()RecordStore.
The BookStore class provides a simpler interface that allows applications
to work in terms of the BookStore itself, instead of dealing with the
underlying RecordStore. Here, for example, are the methods that get the
number of books in the BookStore and allow the BookStore
to be closed. As you can see, they both delegate directly to the corresponding
RecordStore methods:
// Closes the bookstore
public void close() throws RecordStoreException {
if (store != null) {
store.closeRecordStore();
}
}
// Gets the number of books in the bookstore
public int getBookCount() throws RecordStoreException {
if (store != null) {
return store.getNumRecords();
}
return 0;
}