End-To-End BlackBerry Application (Part 5)

July 5, 2008 15:00 by Jorge

This is the fifth part of my end-to-end BlackBerry application walk-through. In this article I will go over the following subjects:

  • Using resource files for localizing text strings without coding changes
  • Setting an application icon on the Ribbon
  • Home Screen’s command list icons
  • Trimming the cached articles list
  • Encoding HTTP request data
  • Application “About” dialog
  • Alerting the user when a long-running operation is taking place

Using Resource Files for Localizing Text Strings

Although an in-depth discussion about resource files is beyond the scope of this post (a good place to see more information on this subject is the BlackBerry Java Development Environment Fundamentals Guide, which you can obtain from the BlackBerry developers site),  I will tell you that each application needs a resource header file, a resource contents file for the root (global) locale and a resource content file for each specific locale it will support.

The resource header file defines descriptive keys for each localized string. Resource content files map descriptive keys to values for the global and specific locales.

Resources for a given locale end up being stored in a ResourceBundle object. A ResourceBundleFamily object groups the resources for an application as a collection of ResourceBundle objects. This is the mechanism an application uses to switch languages depending on the user's locale.

For our application, I created the resource header file (KnowledgeBase.rrh) and a resource contents file for the root locale (KnowledgeBase.rrc). Based on these files, the BlackBerry IDE will create at compile time a resource interface, KnowledgeBaseResource, that we can use to gain access to the localized strings. The resource interface has the same name as the resource header file with the word Resource appended.

To access our localized resources through the application’s resource bundle, we first need to obtain a reference to our resource bundle:

   
private ResourceBundle resources = ResourceBundle.getBundle(KnowledgeBaseResource.BUNDLE_ID,
            KnowledgeBaseResource.BUNDLE_NAME);

Now we’re ready to retrieve a localized string like so

 

String screenTitle = resources.getString(KnowledgeBaseResource.TTL_HOME_SCRN);

Like I said at the beginning of this section, there’s much more to resources than what I’ve covered here. You should plan on learning more and I would suggest you start by checking out the BlackBerry Java Development Environment Fundamentals Guide, available on the BlackBerry developers site.

Setting an Application Icon

It’s time now to replace the default application icon with something nicer. To set the application icon through the BlackBerry JDE, add the image that will serve as the application icon to the Project and bring up its properties:

Setting-App-Icon

In the File Properties applet, select “Use as application icon”:

Setting-App-Icon2

And when we run the application, we see the new application icon:

App-Icon-Ribbon

Note that although this procedure allows you to set the application icon, it does not allow you to change the image when the application is selected or unselected within the Ribbon.  I will describe how to achieve this behavior in a future article.

Home Screen’s Command List Icons

Originally,  the three commands on the Home Screen were preceded by the same icon, a green star:

Home-Screen1

I’ve made some changes to the HomeScreen so that now, each command is preceded by an icon that reflects the command’s function:

Home-Screen-Icons

I achieved this by modifying the code in the MyObjectListField Class within the Home Screen. In a nutshell, MyObjectListField now has three private bitmaps, one per command, and when it’s time to draw the list row, the appropriate bitmap is drawn before the command. This is how the code looks:

private class MyObjectListField extends ObjectListField {
        
        private Bitmap searchIcon = Bitmap.getBitmapResource(resources.getString(KnowledgeBaseResource.IMG_SEARCH));  
        private Bitmap tagsIcon = Bitmap.getBitmapResource(resources.getString(KnowledgeBaseResource.IMG_TAGS_LIST)); 
        private Bitmap recentIcon = Bitmap.getBitmapResource(resources.getString(KnowledgeBaseResource.IMG_VIEW_RECENT)); 
        private Bitmap icon;          
        
        // We are going to take care of drawing the item.
        public void drawListRow(ListField listField, Graphics graphics, 
                int index, int y, int width) {
                    
            if ( mainMenuItems[index] == mnuSearchTitles) {
                icon = searchIcon;
            }   
            if ( mainMenuItems[index] == mnuBrowseTags) {
                icon = tagsIcon;
            }  
            if ( mainMenuItems[index] == mnuViewRecent) {
                icon = recentIcon;
            }   
            
            if (null != icon) {
                int offsetY = (this.getRowHeight() - icon.getHeight())/2;
                graphics.drawBitmap(1,y + offsetY, icon.getWidth(),
                        icon.getHeight(),icon,0,0);
                graphics.drawText(mainMenuItems[index], 
                    icon.getWidth() + 2, y, DrawStyle.ELLIPSIS, width - icon.getWidth() + 2);
            } else {
                graphics.drawText("- " + mainMenuItems[index], 0,
                    y, DrawStyle.ELLIPSIS, width - graphics.getFont().getAdvance("- "));
            }
            
        }
}

Trimming the Cached Articles List

Based on the requirements I set, our application should maintain an in-memory cache with the most recently viewed articles.

The application’s DataStore Class is already capable of handling the storage and retrieval of the cached articles via the setCachedArticles() and getCachedArticles() methods. We also have a way for the user to set the maximum number of cached articles there can be.

What we’re missing is the mechanism to, once a user opens an article, add such article to the cache and prune the cache so the number of articles does not exceed the maximum the user defined. A good place to implement such mechanism is the Article Screen. Let’s add some code that will take care of the possible scenarios:

  1. The cache is empty
  2. The cache has some articles, but it still has room
  3. The cache is full
  4. The article we’re viewing is already cached

When the cache is empty, we only need to add the currently viewed article:

 
// Handle the case when there's nothing cached.
if (null == cachedItems || cachedItems.length == 0) {
            
	cachedItems = new Article[]{article};
	DataStore.setCachedArticles(cachedItems);
	return;
            
}

When the cache is not empty but still has room, we only need to insert the current article at the top of the list:

 
// Handle the case when we need to add one more article to the list.
Article[] newList = new Article[cachedItems.length + 1];
                
// Insert the old items in the new list.
for (int i = 1; i <= cachedItems.length; i++) {
                     
	// We're shifting the articles too, to leave room at the top of the list
	// for the article we're currently viewing.
	newList[i] = cachedItems[i - 1];
                     
}
                
// Now, put the article we're viewing at the top of the list.
newList[0] = article;
                
DataStore.setCachedArticles(newList);

When the cache is full, we have to make room for the for the current article:

 
// Handle the case when the cached need to be trimmed.
Article[] newList = new Article[maxAllowed];
// Insert all the items, except the oldest, in the new list.
for (int i = 1; i < maxAllowed; i++) {
                    
	// We're shifting the articles too, to leave room at the top of the list
	// for the article we're currently viewing.
	newList[i] = cachedItems[i - 1];
                    
} 
                
// Now, put the article we're viewing at the top of the list.
newList[0] = article;
                
DataStore.setCachedArticles(newList);

When the article we’re viewing is already cached we just bail out:

 
// Bail if the article we're viewing is already cached.
for (int i = 0; i < cachedItems.length; i++) {                
	if (article.id.equalsIgnoreCase(cachedItems[i].id)) {
		return;
	}
}

Looks like we took care of all the scenarios, doesn’t it? Well, not quite yet. What happens if the cache has N number of articles and the user, through the Options Screen, changes the maximum number of articles the cache can store to something less than N? To handle this situation we need to add some code to the Options Screen:

 
private void trimCachedArticlesList() {
        
        Article[] cached = DataStore.getCachedArticles();
        
        if (null == cached || cached.length == 0) {
            return;
        }
        
        int maxAllowed = DataStore.getMaxCachedArticles();
        
        if (cached.length <= maxAllowed) {
            return;
        }
        
        Article[] newList = new Article[maxAllowed];
        
        for (int i = 0; i < maxAllowed; i++) {            
            newList[i] = cached[i];
        }
        
        DataStore.setCachedArticles(newList);
        
}

And now I think we’re in good shape. If I forgot some detail I’m sure you will let me know. :)

Encoding HTTP Request Data

On the subject of forgetting details, back when I added the HTTP handling code, the routine that took care of creating the HTTP request data looked like this:

 
public static String createRequestString(final String[] keys, final String[] values) {
        
        StringBuffer requestContents = new StringBuffer("");
        if (keys != null) {               
              for (int i = 0; i < keys.length; i++) {
                 requestContents.append(keys[i] + "=" +  values[i] + "&");              
              }
        }
        // Terminate the request with a valid sequence.
        requestContents.append("0=0\r\n");    
           
        return requestContents.toString();
}

Everything seems fine with this code until you start wondering what would happen if any actual values (any of the values[i]) in the request data contained reserved characters like ";", "/", "?", ":", "=", "&". And it can become as serious as it became for me last week when a production application started “randomly” failing because of this encoding issue.

What we need here is to make sure that our request data is correctly encoded so our server-side application can make sense of the request.

Working to fix my last week’s emergency, I stumbled upon net.rim.blackberry.api.browser.URLEncodedPostData. URLEncodedPostData is precisely a class that encodes form data using URL-encoding. Let’s use it to take care of our encoding issues:

 
public static byte[] createPostData(final String[] keys, final String[] values) {
        
        URLEncodedPostData postData = new URLEncodedPostData(URLEncodedPostData.DEFAULT_CHARSET, true);
        
        if (keys != null) {               
             for (int i = 0; i < keys.length; i++) {
                 postData.append( keys[i], values[i]);              
             }
         }
           
         return postData.getBytes();
}

Application “About” Dialog

I thought of the “About” dialog as a nice touch for our application. It is triggered by the “About” menu that I just added to the Home Screen.

About-Dialog

Of interest here is the use of ApplicationDescriptor to obtain the application name and the version number:

 
private MenuItem aboutMenu = new MenuItem(resources.getString(KnowledgeBaseResource.MNU_ABOUT),150,10) {
       public void run() {
           String crNotice = resources.getString(KnowledgeBaseResource.LBL_ABOUT);
           ApplicationDescriptor descriptor = ApplicationDescriptor.currentApplicationDescriptor();
           String appVersion = descriptor.getName() + " " + descriptor.getVersion();
           Dialog.alert(appVersion + "\n" + crNotice);
       } 
};

Alerting Users When Long-Running Operations Take Place

Some of the features of our application, i.e. searching for articles, can potentially involve long execution times. It’s important that we don’t frustrate our users by starting a long-running operation without giving them some visual clue indicating that our program is actually working and they might need to wait some time for an operation to complete.

Some developers prefer to use screens as status indicators, but a simple dialog does just fine.

Downloading-Dialog

This is how it’s done:

 
Bitmap icon = Bitmap.getBitmapResource(resources.getString(KnowledgeBaseResource.IMG_DOWNLOAD)); 
statusDlg = new Dialog(resources.getString(KnowledgeBaseResource.LBL_DOWNLOADING),null,null,0,icon); 

private void sendHttpRequest(String[] keys, String[] values) {
        
       String url = DataStore.getAppServerUrl();
       // Make sure we can create an http request.
       // If the app. server URL has not been set, we cannot make any http requests.
        if (null == url || url.length() == 0) {
            Dialog.alert(resources.getString(KnowledgeBaseResource.ERR_INVALID_APP_SERVER_URL));
            return;
        }
        
        statusDlg.show();

	.
	.
	.
}

public void processResponse(final int HTTPResponseCode, final String data) {
        UiApplication.getUiApplication().invokeLater(new Runnable() {
            public void run()
            {
                statusDlg.close();

	.
	.
	.
}

What's Next

Having covered the above important details, we still need to write the server-side code to handle incoming HTTP requests, retrieve information from the database and send responses back to the application on the BlackBerry device.

Downloads

Download the source code for this article from the Downloads page.

Actions: E-mail | Permalink | Comments (0) | Trackback

How to Build a Real-World BlackBerry Application (Part 4)

June 23, 2008 21:45 by Jorge

For the past three posts I’ve been 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.

In the third part of this series I finished the areas of the application that store and retrieve data from the device’s memory. Together with storing data on the device, the ability to use the network is vital for most applications. Today I will be showing you how the application will communicate with its server-side service.

But first let’s do a little refactoring.

The Options Class is now the DataStore Class

Yes, after working with the Options Class for a while I decided that, since it not only deals with application settings, a better name for it would be DataStore. “DataStore” explains better the intent of the class.

Making HTTP requests

There is a plethora of information regarding HTTP connections. I will leave most of it out of this article and focus only on what’s strictly needed for our application to be able to send HTTP requests and consume HTTP responses.

Two things I should mention are the javax.microedition.io.HttpConnection Interface, which defines the necessary methods and constants for an HTTP connection, and javax.microedition.io.Connector, the Factory Class for creating connections

For my networking tasks I have reused and adapted some code that I obtained from the existing BlackBerry sample code online. The bulk of the HTTP handling code will conform to contracts defined in two separate interfaces HTTPTransport and HTTPTransportListener.

HTTPTransport Interface and the KbHTTPTransport Class

HTTPTransport is an interface that defines the methods for creating HTTP requests.

001/**
002 * Defines the methods to send HTTP requests. 
003 */
004public interface HTTPTransport {
005    
006    public void send(final String requestMethod, 
007        final String URL, final String requestData);
008    public void send(final String requestMethod, 
009        final String URL, final String requestData, 
010        final String[] headerKeys, final String[] headerValues);
011    public void setHTTPTransportListener(HTTPTransportListener t);
012    public void removeHTTPTransportListener();
013    
014}

KbHTTPTransport is a helper class which can make HTTP requests by implementing the methods defined in HTTPTransport. KbHTTPTransport will notify an attached HTTPTransportListener with the response to the request. HTTPTransportListener is an interface that defines the methods a class must have in order to process the response from an HTTP request.

Implementing HTTPTransportListener inside the Articles Screen

So far, the only place in the application where there’s a need to send HTTP requests is the Articles Screen. Implementing HTTPTransportListener enables our Articles Screen to process HTTP responses:

001
002 class ArticlesScreen extends MainScreen implements HTTPTransportListener {
003	
004	.
005	.
006	.
007
008    public void processResponse(final int HTTPResponseCode, final String data) {
009        UiApplication.getUiApplication().invokeLater(new Runnable() {
010            public void run()
011            {
012                switch (HTTPResponseCode) {
013            
014                    case (KbHTTPTransport.NOT_IN_COVERAGE): 
015                        Dialog.alert("Not in coverage.");
016                        break;
017                    case HttpConnection.HTTP_OK:
018                        parseResponseData(data);
019                        break;
020                    // TODO: Check all the http error codes you are interested in.
021                    // for example HttpConnection.HTTP_FORBIDDEN
022                    default:
023                    Dialog.alert("There was an error with the request.");
024                    break;
025                        
026                    }
027            }
028        });
029        
030    }
031

Choosing a transmission format

From the multiple formats for transmitting serialized data over the net, namely xml, binary, etc, I decided to send and receive the data in the form of strings, where the contents of a response are a custom-formatted string that represents a list of articles.

A formatted list of articles would look like this

id^~^title^~^dateCreated^~^author^~^tag1,tag2,...^~^contents~^~

where the sequence “^~^” separates the fields of an article, and the sequence “~^~” separates one article from the next.

Notice how this is a pretty simple format that is both easy to use and light in terms of the number characters transmitted (think about how verbose XML is).

parseResponseData() is where we de-serialize the response string and create a list of Article instances

001
002    private void parseResponseData(String data) {
003        
004        // Parse out each article from the string data
005        // Format: id^~^title^~^dateCreated^~^author^~^
006        //tag1,tag2,...^~^contents~^~<NEXT RECORD>
007        
008        Vector articlesVector = new Vector();
009        int index = 0;
010        int length = data.length();
011        while (index != -1 && index < length - 1){
012            
013            if (index == 0) index = -1;
014            
015            Article article = new Article();
016              
017            article.id = data.substring(++index , 
018                    index = data.indexOf(FIELD_SEPARATOR, index));
019            index += 3;
020            article.title = data.substring(index , 
021                    index = data.indexOf(FIELD_SEPARATOR, index));
022            index += 3;
023            article.dateCreated = data.substring(index, 
024                    index = data.indexOf(FIELD_SEPARATOR, index));
025            index += 3;
026            article.author = data.substring(index, 
027                    index = data.indexOf(FIELD_SEPARATOR, index));
028            index += 3;
029            String tags = data.substring(index, 
030                    index = data.indexOf(FIELD_SEPARATOR, index));
031            article.tags  = StringUtilities.stringToWords(tags);
032            index += 3;
033            article.contents = data.substring(index, 
034                    index = data.indexOf(RECORD_SEPARATOR, index)); 
035            index += 2;           
036            articlesVector.addElement(article);
037        }
038        
039        articles = new Article[articlesVector.size()];
040        articlesVector.copyInto(articles);
041        articlesList.set(articles);        
042    }
043

Testing the networking code

At this point in time I need to be able to test my networking code without the presence of the server side code (it doesn’t exist yet). In order to achieve this I have built an implementation of HTTPTransport, MockHTTPTransport, which creates different kinds of responses that simulate the scenarios that could occur when working with HTTP requests – timeouts, failed authentication, etc.

001
002 /**
003 * Helper to test the network functionality in the Articles Screen without
004 * having a server side application.
005 */
006 class MockHTTPTransport implements HTTPTransport {
007    
008    private HTTPTransportListener listener = null;
009     
010    MockHTTPTransport() {    }
011    
012    public void setHTTPTransportListener(HTTPTransportListener t){
013        listener = t;
014    }    
015    
016    public void removeHTTPTransportListener(){
017        listener = null;
018    }    
019    
020    public void send(final String requestMethod, final String URL, 
021        final String requestData){
022        send(requestMethod, URL, requestData, null, null);;
023    }
024    
025    public void send(final String requestMethod, final String URL, 
026        final String requestData, final String[] headerKeys, 
027        final String[] headerValues) {                                           
028        
029        // Since this is just for testing purposes, 
030        // in here we will return a list or articles, 
031        // as if we had just gotten them via an http request.
032        createArticlesListResponse();     
033                                           
034    }
035

001
002    private void createArticlesListResponse() {        
003        // Create the articles.
004        Article[] articles = new Article[15];
005        Article article;
006        for (int i = 0; i < 15; i++) {          
007          article = new Article();
008          article.id = String.valueOf(i);
009          article.title = "Dummy article " + Integer.toString(i);
010          article.author = "ramonj";
011          article.contents = "This is a test article";
012          article.tags = new String[] {"tag 1", "tag 2", "tag 3"};
013          article.dateCreated = "01/01/2008";
014          articles[i] = article;          
015        }
016        String FIELD_SEPARATOR = "^~^";
017        String RECORD_SEPARATOR = "~^~";        
018        //Create the the simulated response data 
019        //with the information from the articles.
020        StringBuffer response = new StringBuffer();
021        for (int i = 0; i < articles.length; i++) {            
022            article = articles[i];
023            StringBuffer tags = new StringBuffer();
024            for (int j = 0; j < article.tags.length; j++) {
025                tags.append(article.tags[j] + ",");
026            }
027            // Remove the last ","
028            tags.deleteCharAt(tags.length() - 1);            
029            // Format: id^~^title^~^dateCreated^~^author^~^
030            //tag1,tag2,...^~^contents~^~<NEXT RECORD>
031            response.append(article.id + FIELD_SEPARATOR + 
032                article.title + FIELD_SEPARATOR +
033                article.dateCreated + FIELD_SEPARATOR + article.author + 
034                FIELD_SEPARATOR + tags + FIELD_SEPARATOR + article.contents +
035                RECORD_SEPARATOR);            
036        }         
037        int httpResponseCode  = HttpConnection.HTTP_OK;        
038        // Introduce a delay to simulate latency.
039        try {
040            Thread.sleep(1500);
041        } catch (InterruptedException ex) {}        
042        listener.processResponse(httpResponseCode,response.toString());    
043    }
044
045

What’s next

The next article of this series will be dedicated to the server side of the application. On the server side I will put together the code to handle incoming HTTP requests, retrieving information from the database and sending responses back to the application on the BlackBerry device.

Downloads

Download the source code for this article from the Downloads page.

Actions: E-mail | Permalink | Comments (0) | Trackback

How to Build a Real-World BlackBerry Application (Part 3)

June 5, 2008 15:47 by Jorge

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.

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


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

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”, right-clicking and choosing the [Convert “KnowledgeBase” To Long] menu item.

The object that I chose to serve as 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

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

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

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


Options Screen

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

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’s 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

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

The last item for this article is a very important one and has to do with the fact that any objects that we’d like to commit to the Persistent Store have 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


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 device.

Downloads

Download the source code for this article from the Downloads page.

 

Actions: E-mail | Permalink | Comments (0) | Trackback