.

End-To-End BlackBerry Application (Part 6)

July 15, 2008 18:01 by Jorge

In this sixth delivery of my end-to-end BlackBerry application walk-through, I will cover the following topics:

  • Creating a .NET http handler to send and receive information from the handheld application
  • Consuming the server-side business logic services from our http handler
  • Making requests from the handheld application and receiving data

While the previous articles of this series dealt with creating the Java application that will be installed on the handheld device, today I will be working on the server-side modules. Let’s take another look at our basic building blocks:

Building-Blocks1

The server-side modules consist of a .NET HTTP handler, a set of class libraries that contain the business logic and data access code, and a SQL Server database that will serve as the articles repository of our Knowledge Base application.

Creating The HTTP Handler

The mission of  the HTTP Handler is to extract the information from the HTTP requests made from the handheld, determine what type of action is required and ask the business logic layer to execute such actions. It will also append the results produced by the business logic to HTTP responses and send them to the handheld application.

In order for the handheld application and the handler to be able to talk to each other, all the commands that the handheld application can possibly send need to be defined within the handler as they were defined in the handheld:

// The commands the handheld can send us.
private const int CMD_SEARCH_ARTICLES = 1;
private const int CMD_GET_ARTICLES_BY_TAG = 2;
private const int CMD_GET_TAG_COUNTS = 3;

The keys identifying  each of the request parameters need to be defined as well:

// The request keys.
private const string KEY_COMMAND = "cmd";
private const string KEY_SEARCH_PHRASE = "sp";
private const string KEY_TAG = "tg";

KEY_SEARCH_PHRASE identifies the request parameter carrying the search phrase when a request to search the existing articles is made. KEY_TAG identifies the parameter carrying the tag name when the request is to retrieve the articles for a given tag.

Determining what to do is a matter of inspecting the request parameter marked with the KEY_COMMAND key:

string commandString = request.Form[KEY_COMMAND];
 
if (!int.TryParse(commandString, out currentCommand)) 
{
    // Bail if we don't receive a numeric command.
    return;
}
 
articlesServer = Shared.GetArticlesServer(context);
 
switch (currentCommand)
{
    case CMD_SEARCH_ARTICLES:
        string searchPhrase = request.Params[KEY_SEARCH_PHRASE];
        responseString = SearchArticles(searchPhrase);
        break;
    case CMD_GET_ARTICLES_BY_TAG:
        string tag = request.Params[KEY_TAG];
        responseString = GetArticlesForTag(tag);
        break;
    case CMD_GET_TAG_COUNTS:
        responseString = GetTagCounts();
        break;
    default:
        return;
}

Consuming The Business Logic Services

I won’t go into the details of the construction of the business logic and data access layers today, since they were covered in my End-to-end ExtJS application series. Something noteworthy, though, is the fact that these layers support storage of the articles in a file or in a SQL Server database. The code download for today’s article defaults to file-based storage, but you can run the included SQL script to install the database and point the business layer to it by changing the web application’s configuration file.

In the HTTP handler, SearchArticles(), GetArticlesForTag() and GetTagCounts() simply forward the requests to the business logic layer:

private string SearchArticles(string searchPhrase)
{
    Debug.Assert(searchPhrase != null);
    if (null == searchPhrase)
    {
        ExceptionPolicy.HandleException(
                new ArgumentNullException("searchPhrase"), 
                Shared.KB_EXCEPTION_PLCY);
    }
 
    string result = string.Empty;
 
    try
    {
        List<IArticle> articles = 
            articlesServer.GetArticlesForSearchPhrase(searchPhrase);
        if (null == articles) return 
                string.Empty;
        result = SerializeArticlesList(articles);
    }
    catch (Exception ex)
    {
        ExceptionPolicy.HandleException(ex, Shared.KB_EXCEPTION_PLCY);
    }
 
    return result;
}
private string GetArticlesForTag(string tag)
{
    Debug.Assert(tag != null);
    if (null == tag)
    {
        ExceptionPolicy.HandleException(
            new ArgumentNullException("tag"), 
            Shared.KB_EXCEPTION_PLCY);
    }
 
    string result = string.Empty;
 
    try
    {
 
        List<IArticle> articles = articlesServer.GetArticlesForTag(tag);
        if (null == articles) return
                string.Empty;
        result = SerializeArticlesList(articles);
    }
    catch (Exception ex)
    {
        ExceptionPolicy.HandleException(ex, Shared.KB_EXCEPTION_PLCY);
    }
 
    return result;
}
private string GetTagCounts()
{
    StringBuilder sb = new StringBuilder("");
 
    try
    {                
        Dictionary<string, int> tagCounts = articlesServer.GetTagCounts();
 
        if (null != tagCounts && tagCounts.Count > 0)
        {
            foreach (KeyValuePair<string, int> tagStat in tagCounts)
            {
                sb.Append(string.Format("{0}^~^{1}~^~", 
                    tagStat.Key, 
                    tagStat.Value));                       
            }
        }
 
    }
    catch (Exception ex)
    {
        ExceptionPolicy.HandleException(ex, Shared.KB_EXCEPTION_PLCY);
    }
    return sb.ToString();
}

After the business logic results are delivered to the handler I need to serialize the results according the formatting convention I had established when I wrote the handheld code. For example, this is the routine that converts a list of articles into a string that will be sent to the handheld as part of the HTTP response:

private string SerializeArticlesList(List<IArticle> articles)
{
    Debug.Assert(articles != null);
    if (null == articles)
    {
        ExceptionPolicy.HandleException(
            new ArgumentNullException("articles"), 
                Shared.KB_EXCEPTION_PLCY);
    }
 
    StringBuilder sb = new StringBuilder("");
    string body;
 
    foreach (IArticle article in articles)
    {
        // Decode the html characters in the body of the article.
        body = HttpUtility.HtmlDecode(HttpUtility.UrlDecode(article.Body));
        // Strip the html characters (We're using a RichTextField on the handheld,
        // which isn't capable yet of displaying html.
        body = Regex.Replace(body, @"<(.|\n)*?>", string.Empty);
 
        sb.Append(string.Format("{0}^~^{1}^~^{2}^~^{3}^~^", 
            article.Id.ToString(), 
            article.Title, 
            article.DateCreated.ToShortDateString(), 
            article.Author));
        sb.Append(string.Format("{0}^~^{1}~^~", 
            article.Tags, body));
    }
 
    return sb.ToString();
}

Did you notice that in the code above I’m also removing the Html characters from the body of the article? I have to do this because the field I’m using to display the body of the article on the handheld is not capable of rendering Html.

All that’s left now is to send the data to the handheld:

response.ContentType = "text/plain";
int contentLength = response.Output.Encoding.GetByteCount(responseString);
response.AppendHeader("content-length", contentLength.ToString());
response.Write(responseString);
response.End();

Well,  I know the walk through the server-side code went pretty fast. I encourage you to download the code for today’s post and check it out to see all the details. Also, reading the End-to-end ExtJS application post can give you more insight on the business logic and data access layers.

Making Requests From The Handheld Application And Receiving Data

Let’s return to the device-side code and ready it to connect to the HTTP handler. The first thing I will do is stop using the MockHTTPTransport instance and start using KbHTTPTransport. While MockHTTPTransport simply simulates an HTTP request to allow for testing of all the rendering logic on the device, KbHTTPTransport is the real deal. This is how I use it from the Articles Screen or the Tags Screen:

private void sendHttpRequest(String[] keys, String[] values) {
   
   articlesList.set(null);  // Show "No Articles" while we download.
    
   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();
    
    byte[] requestContents = TransportUtils.createPostData(keys, values);
    
    KbHTTPTransport transport = new KbHTTPTransport();
    transport.setHTTPTransportListener(this);
    transport.send(KbHTTPTransport.REQ_METHOD_POST, url, requestContents);        
    
}

At this point all the pieces are in place and I’m going to use the BlackBerry simulator and MDS simulator to test the application with real data. After firing up both simulators, I will check the Options Screen and make sure that I’m pointing to the right URL for my HTTP handler:

Options Screen

Back to the Main Screen, I choose Browse Tags:

image

And here I am getting real data from the handler:

image

Selecting a tag should bring back some articles:

image

And selecting an article should display its contents:

image

Conclusion

I hope you’ve found this series of posts helpful. I think the application is at a reasonable quality level where you can use its blocks as a foundation for your own projects. Although I’ll move on into other topics, I’m willing to come back to work on or talk more about any areas of your interest. Please let me know your thoughts. 

Downloads

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

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

How-To: Signing Your BlackBerry Application Before Deployment

July 9, 2008 19:24 by Jorge

A concerned reader asked me about how to sign his BlackBerry application. In this post I will try to provide you with information on the following topics:

  • Why the need to sign your BlackBerry code
  • Registering and obtaining signature keys
  • Signing your application

Why You Need to Sign Your Code

The concept of “controlled” API's has existed since BlackBerry 3.6 as a way for Research In Motion (RIM) to track the use of some API's for security and export control reasons. In practical terms, this means that in order to run an application on a handheld, you need to register and sign the application. Signing of applications is not required to run applications using the BlackBerry device simulator.

The BlackBerry API is divided into five groups. The first group includes all standard Java API's from MIDP and CLDC and some BlackBerry-specific API's. This group is open for all developers, and applications that use only these open API's require no signatures. The remaining four groups are all controlled API's: RIM Runtime API's, some BlackBerry API's, RIM Cryptography and Certicom Cryptography.

Registering and Obtaining Signature Keys

The registration process serves to verify the developer's identity. It involves downloading and filling out a registration form that you need to fax to RIM. There’s also a one-time processing fee associated with it. RIM will send you a set of signature keys after the registration form and fee are received.

Signing Your Application

Once you receive the signature keys from RIM, you need to install them on your development environment. The detailed instructions on how to do this can be found in the BlackBerry Java Developer Guide, Volume 2 - Advanced Topics. I recommend that you study this procedure, since installing the keys incorrectly will cause the signing process to fail.

Signing the application is very easy once the keys are installed. You need to manually start the Signature Tool from the Build menu in the JDE:

Request-Signatures-Menu

The tool displays a list of necessary signatures:

 Signature-Tool1

When you press the Request button, the Signature Tool submits a hash of the application to RIM's signing authority. The signing authority automatically returns the required signature, that is automatically appended to the application. The application can be loaded onto a device after this step.

Note that RIM does not receive a copy of your application, only a hash of the file(s). This allows them to determine the author of the application by matching the hash of the application against records of the hash kept by the signing authorities.

Where to Find More Information

You can find more information on this topic in the BlackBerry Java Developer Guide, Volume 2 - Advanced Topics.

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

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(&quot;- &quot; + mainMenuItems[index], 0,
                    y, DrawStyle.ELLIPSIS, width - graphics.getFont().getAdvance(&quot;- &quot;));
            }
            
        }
}

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 &lt;= 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(&quot;&quot;);
        if (keys != null) {               
              for (int i = 0; i < keys.length; i++) {
                 requestContents.append(keys[i] + "=" +  values[i] + &quot;&amp;&quot;);              
              }
        }
        // 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: