Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

End-to-End Real World BlackBerry Application, Part 3

0.00/5 (No votes)
13 Jul 2008 1  
End-to-end real world BlackBerry application walkthrough, Part 3.

Introduction

This is the third part of a series of articles where I am walking you through the creation of an end-to-end BlackBerry application that will serve as a mobile front-end to my Knowledge Base sample web application.

Home-Screen1.gif

In my previous article of this series, I finished building the screens that serve as the application’s user interface. I also added some test code in order to test the interface against some basic usage scenarios.

In this article, I will cover the subject of how to store and retrieve data from the device’s flash memory. This is a very important subject since most applications have no value without the ability to preserve some state across device resets.

What data do we need to save?

  1. The URL of our application server.
  2. A list of recently viewed articles.
  3. The maximum number of recently viewed articles the above list should contain.

Items 1 and 3 are application settings, and their storage and retrieval will occur within the context of the Options Screen.

The list of recently viewed articles will be built as the user opens articles. It will be committed to memory upon application shutdown.

Storing Data Using the Persistent Store

The BlackBerry API provides for persisting information across device resets through the Persistent Store (PersistenStore class). Persistent objects (PersistentObject class) are objects whose contents can persist across device resets. These objects (in the form of key-value pairs) can be committed to the persistent store, and later retrieved via the key. Here's a sample usage of the persistent store, taken from the BlackBerry API documentation:

static Vector addresses;
static PersistentObject persist;
 
static {
         
   long KEY =  0xa3b3159378f59a29L;
   persist = PersistentStore.getPersistentObject( KEY );
   addresses = (Vector) persist.getContents();
   if( addresses == null ) {
       addresses = new Vector();
       persist.setContents( addresses );
       persist.commit()
   }
}
     
void add( Address a ) {
   addresses.addElement( a );
   persist.commit();
}

Options Class

The Options class encapsulates all the code related with storing and retrieving the data we need to persist across device resets. The class constructor will be in charge of retrieving our application’s persistent object from the persistent store, like so:

private Options() {
    store = PersistentStore.getPersistentObject(0xf6354ee14c6cc857L);
}

The constructor is private in order to reinforce a singleton behavior that will prevent the existence of multiple instances of our application’s store being worked with at the same time.

The number 0xf6354ee14c6cc857 is a hash of “KnowledgeBase”. You can obtain this hash by selecting the package name at the top of the file, in this case “KnowledgeBase”, and right-clicking and choosing the [Convert “KnowledgeBase” To Long] menu item.

The object that I chose to serve as a container for the application state that needs to be persisted, and that in turn will be contained by the Persistent Object instance that we will commit to the persistent store, is a LongHashtableCollection, which is a Hashtable collection using long integers as keys.

Here’s a diagram that will help you visualize how our data will be stored in memory:

Persistent-Store-Diagram.gif

I created a couple of private methods, set(long key, Object value) and get(long key), that will directly deal with setting and getting the contents of the persistent store.

private void set(long key, Object value) {
    synchronized(store) {
        settings = (LongHashtableCollection)store.getContents();
        if (null == settings) {
            settings = new LongHashtableCollection();
        }
        settings.put(key,value);   
        store.setContents(settings);
        store.commit();
    }
}    

private Object get(long key) {
    synchronized(store) {
        settings = (LongHashtableCollection)store.getContents();
        if (null != settings && settings.size() != 0) {
             return settings.get(key);
        } else {
             return null;
        }
    }
}

Each distinct piece of data (application setting, etc.) to commit to the store will be identified by a unique key.

private static final long KEY_APP_SRV_URL = 0;
private static final long KEY_MAX_CACHED_ARTICLES = 1;
private static final long KEY_CACHED_ARTICLES = 3;

And, based on these pieces of data, these are the methods that the consumers of the Options class can use:

public static String getAppServerUrl() {
   String url = (String)getInstance().get(KEY_APP_SRV_URL);
   if (null == url || url.length() == 0) {
     url =  DEFAULT_SERVICE_URL; 
     setAppServerUrl(url);
   }
   return url;
}

public static void setAppServerUrl(String url) {
   getInstance().set(KEY_APP_SRV_URL,url);
} 

public static int getMaxCachedArticles() {
   int maxCachedArticles;
   String maxCachedArticlesString = 
            (String)getInstance().get(KEY_MAX_CACHED_ARTICLES);
    if (null == maxCachedArticlesString || 
        maxCachedArticlesString.length() == 0) {
        maxCachedArticles =  DEFAULT_MAX_CACHED_ARTICLES; 
        setMaxCachedArticles(maxCachedArticles);
    } else {
        maxCachedArticles = Integer.parseInt(maxCachedArticlesString);
    }
   return maxCachedArticles;
}

public static void setMaxCachedArticles(int maxCachedArticles) {
   getInstance().set(KEY_MAX_CACHED_ARTICLES,String.valueOf(maxCachedArticles));
} 

public static Article[] getCachedArticles() {
    Article[] articles = (Article[])getInstance().get(KEY_CACHED_ARTICLES);
   if (null == articles ) {
     articles =  new Article[0]; 
     setCachedArticles(articles);
   }
   return articles;
}

public static void setCachedArticles(Article[] articles) {
   getInstance().set(KEY_CACHED_ARTICLES,articles);
}

Options Screen

Within the Options Screen, displaying and saving the application settings is pretty straightforward. These operations are handled by displayOptions() and saveOptions().

public void displayOptions() {
        
    String serverUrl = Options.getAppServerUrl();
    int maxCachedArticles = Options.getMaxCachedArticles();
    
    appServerUrlField.setText(serverUrl);
    maxCachedArticlesField.setText(String.valueOf(maxCachedArticles));
    
}

private void saveOptions() {
    
    String serverUrl = appServerUrlField.getText().trim();
    String maxCachedArticlesString = maxCachedArticlesField.getText().trim();
    
    if (null == serverUrl || serverUrl.length() == 0 ||
        null == maxCachedArticlesString || 
            maxCachedArticlesString.length() == 0) {
          
          Dialog.alert("Please correct the invalid options");
          return;  
    }
    
    Options.setAppServerUrl(serverUrl);
    Options.setMaxCachedArticles(Integer.parseInt(maxCachedArticlesString));
    this.setDirty(false);
    this.close();
    
}

Note how saveOptions() does a simple validation of the data before committing it to memory via our Options class.

Articles Screen

Let’s modify the test code within the ActiclesScreen class’ constructor to take advantage of our Options class ability to save and retrieve a list of recently viewed articles. Before, our test code just created a list of dummy articles in order to display them on the screen.

ArticlesScreen() {
        
    this.setTitle("Articles");
    
    // Create a few dummy articles in order to test the screen;
    articles = new Article[15];
    Article article;
    for (int i = 0; i < 15; i++) {
        article = new Article();
        article.title = "Dummy article " + Integer.toString(i);
        article.author = "ramonj";
        article.contents = "This is a test article";
        article.tags = new String[] {"tag 1", "tag 2", "tag 3"};
        article.dateCreated = "01/01/2008";
        articles[i] = article;
     }

    articlesList = new ArticlesListField();
    articlesList.set(articles);
    this.add(articlesList);

}

Now, let’s first check if there are any articles cached, and if not, create our dummy articles and commit them to the device’s memory.

ArticlesScreen() {
        
    this.setTitle("Articles");
    
    articles = Options.getCachedArticles();
    
    if (null == articles || articles.length == 0) {
    
        // Create a few dummy articles in order to test the screen;
        articles = new Article[15];
        Article article;
        for (int i = 0; i < 15; i++) {
            article = new Article();
            article.title = "Dummy article " + Integer.toString(i);
            article.author = "ramonj";
            article.contents = "This is a test article";
            article.tags = new String[] {"tag 1", "tag 2", "tag 3"};
            article.dateCreated = "01/01/2008";
            articles[i] = article;
        }
        
        Options.setCachedArticles(articles);

    }
    
    articlesList = new ArticlesListField();
    articlesList.set(articles);
    this.add(articlesList);

}

The last item for this article is a very important one, and has to do with the fact that any object that we’d like to commit to the Persistent Store has to implement the net.rim.device.api.util.Persistable interface. Since we are storing instances of the Article class as a way to persist a list of recently viewed articles across device resets, the Article class must explicitly implement this interface. Here’s the code:

import net.rim.device.api.util.Persistable;

class Article implements Persistable {
    public String title;
    public String dateCreated;  
    public String author;
    public String[] tags;
    public String contents;
}

What’s Next

Well, this is it for now. In the next article of this series, I will add the networking code. This will pretty much take care of the device-side code and set the stage for moving to the server side. On the server side, I will put together the pieces that will handle the communications with the handheld.

Previous Articles

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here