Welcome!

Welcome to the official BlackBerry Support Community Forums.

This is your resource to discuss support topics with your peers, and learn from each other.

inside custom component

Java Development

How to track progress of page loads in your BrowserField2 application

by Retired on ‎10-29-2010 04:34 PM (7,062 Views)

 

Have you ever wondered how to improve the user experience while a Webpage is loading in your BrowserField2 application?

 

What about a progress bar?

 

Do you think it is quite complicated to create a progress bar because the number of resources that will load with your page are unknown beforehand? Hmm, sounds like you're right...but there's hope.

 

This article will explain in detail how to create a progress bar that keeps track of page loads in your BrowserField2 application (BlackBerry® 6) and that can significantly improve the user experience.

 

 

 

Motivation:


The BrowserField2 API provides a set of powerful components that allow developers to embed web content into a BlackBerry® Java® application. While embedding web content can be extremely beneficial, caution is required to provide users with the best UI experience possible. For instance, loading HTML pages using a BrowserField can be a time-consuming task especially in the case of large pages containing a fairly large number of resources (e.g., images, CSS, Javascript®). As a consequence, users can feel a bit lost and start repeating actions while a page is loading as little progress information is displayed.


Goal of this Article:


In this article we explain how to track and display progress information when a page is loading in a BrowserField. At first sight displaying progress information on the screen does not seem a complex task at all. However, computing progress information can be quite tricky in the case of web pages in which the number of resources to load is not known beforehand. As a consequence, there is no way to anticipate the total size of the page. In the following, we provide a complete example on how to compute and display load progress information in a BrowserField2 application even when the size of the page is initially unknown.

 

Ultimately, we hope that the example provide can server as a starting point for developers to create even better interfaces that can raise the user experience to new levels.


Sample Application:

 

In the following, we introduce a Mini-Browser application (see Figure 1) that explains how to create a progress bar that displays progress information about a page loading in a BrowserField. The application contains three classes that will be covered next: BrowserField2ProgressTracker (application's entry point), BrowserFieldScreen (application's main screen), and BrowserFieldLoadProgressTracker (the main point of the article).

 

 

bb_loading_tracker_small.PNG

 

Figure 1: The Mini-Browser sample application displaying a progress bar while RIM's web page is loading

 

 

Class BrowserField2ProgressTracker:

 

This class represents the Mini-Browser application's entry point as it defines the main() method. The application extends class UiApplication as we need to display information on the UI using a BrowserField . In addition, the class pushes a screen called BrowserFieldScreen that contains a location bar, a progress bar and a browser field as we will discuss next.

 

 

public class BrowserField2ProgressTracker extends UiApplication {

public static void main(String[] args){
BrowserField2ProgressTracker theApp = new BrowserField2ProgressTracker();
theApp.enterEventDispatcher();
}

public BrowserField2ProgressTracker() {
pushScreen(new BrowserFieldScreen());
}
}

 

Class BrowserFieldScreen:

 

Class BrowserFieldScreen represents the Mini-Browser's main screen. Notice that this class consists of three UI components: a TextField, a GaugeField and a BrowserField. The TextField represents the location bar where the user will type the URL of the page to be loaded in the BrowserField. The GaugeField represents the progress bar that will display progress information about the page loading in the BrowserField. Finally, the BrowserField  is the UI component that will render HTML pages.

 

The class also contains a non-visual component called progressTracker that is responsible for computing (estimating, actually) the progress percentage value for the current loading page. The value (a fraction between 0 and 1.0) provided by the tracker is used to update the progress bar (GaugeField).

 

 

class BrowserFieldScreen extends MainScreen {

// The location bar where URL will be typed in
private TextField locationBar;

// The progress bar
private GaugeField progressBar;

// The BrowserField
private BrowserField browserField;

// The progress tracker object that updates the progress bar
private BrowserFieldLoadProgressTracker progressTracker;

...

 

The BrowserFieldScreen's constructor basically creates the progress tracker (class BrowserFieldLoadProgressTracker) object and creates/arranges the UI components that comprise the Mini-Browser application.

 

 

public BrowserFieldScreen() {
progressTracker = new BrowserFieldLoadProgressTracker(10f);
createGUI();
}

 

The createGUI() method simply creates the location bar, the progress bar, and the browser field UI components and arranges those vertically using a VerticalFieldManager manager. The <ENTER> key pressed event is tracked down and will result in a new URL (as typed in the location bar) being requested to the browser field. Notice that the progress bar is reinitialized (call to reset()) for every new request (progressBar.reset()).

 

The location bar is represented by a TextField component that will capture the URL typed by the user and to be used to be load pages in the BrowserField (just like a Browser app).

 

Finally, the browser field component is created using a specific method: createBrowserField() that will be discussed next.

 

 

private void createGUI() {
        
   VerticalFieldManager mainManager =
new VerticalFieldManager(Field.USE_ALL_WIDTH | Field.USE_ALL_HEIGHT );
        
   locationBar = new TextField() {

      protected boolean keyChar(char key, int status, int time) {
         if ( key == Characters.ENTER ) {
          Application.getApplication().invokeLater( new Runnable() {
          public void run() {                        
             progressBar.reset("", 0, 100, 0);
               browserField.requestContent(locationBar.getText().trim());
            }
          });
          return true;
         }
         return super.keyChar(key, status, time);
      }
   };
        
   locationBar.setBackground(BackgroundFactory.createSolidBackground(Color.BEIGE));
   locationBar.setText("http://");

   progressBar = new GaugeField( "",0,  100, 0, Field.USE_ALL_WIDTH );

   browserField = createBrowserField();

   mainManager.add(locationBar);
   mainManager.add(progressBar);
   mainManager.add(browserField);
        
   add(mainManager);
        
}

 

The browser field object is created on a specific method called createBrowserField(). Within this method a BrowserFieldListener object is attached to the BrowserField object in order to handle download progress and document load events (see addListener() below). The downloadProgress() method is called by the BrowserField2 API multiple times while a web page is loading to signal that either a new resource was found or that more bytes have been read for a given resource. Note that the progress tracker is updated with each call to downloadProgress() in order to estimate a progress percentage value. The value returned is then used to update the progress bar and display that information to the user. We multiply the returned float value by a 100 and convert it to integer to adapt it to the progress bar value range (0-100).

 

The documentLoaded() method is fired as soon as the document has been fully loaded (including all its resources in BlackBerry 6). At this time, the progress tracker is reset and the progress bar is fully filled out to indicate the load progress is done.

 

As the last statement of the method, the browser field object created is returned.

 

 

private BrowserField createBrowserField() {	    

BrowserField browserField = new BrowserField();

browserField.addListener( new BrowserFieldListener() {
public void downloadProgress(BrowserField browserField,
final ContentReadEvent event) throws Exception {
Application.getApplication().invokeLater( new Runnable() {
public void run() {
int value = (int)(100*progressTracker.updateProgress(event));
progressBar.setValue(value);
}
});
}

public void documentLoaded(BrowserField browserField,
Document document) throws Exception {
Application.getApplication().invokeLater( new Runnable() {
public void run() {
progressTracker.reset();
progressBar.setValue(100);

}
});
}
});

return browserField;
}

} // end of class

 

 

Class BrowserFieldLoadProgressTracker: 

 

The BrowserFieldLoadProgressTracker class is responsible for estimating a percentage value for the load progress of a browser field's loading page. The challenge in computing this value comes from the fact that the number of resources to load is unknown beforehand.

 

The trick in our solution is to split the progress bar in an infinite number of "percentage slots", one for each resource found. As resources are loaded, the slot reserved for that resource is filled out. Figure 2 below, illustrates the approach. The figure shows three slots for a loading web page with sizes 50%, 25%, and 12.5% for page resources A, B, and C, respectively. In this particular example, each next slot is half the size of the previous one. We call this the progress factor, in this case a factor of 2 means 1/2 slot reduction each time (half).

 

 

 

progress_bar.PNG

 

Figure 2 - How resource's slots are combined to compute a final value for the progress bar

 

The first slot is used by resource A and it account for up to 50% of the progress bar. The second slot was created as soon as resource B was discovered and it measures 25%, i.e., half of the size of slot A (because the progress factor is 2) 25%. The same applies to resource C's slot that measures 12.5%. When resource A is fully loaded, 50% of the visible progress bar (orange bar in the Figure) is filled out.

 

Figure 2 shows resource A partially loaded which accounts for 28% of the 50% slot. Resource B is fully loaded (25% out of 25% of its slot) and resource C is almost finished with 10% out of 12.5%. As more resources are found more slots are created. Since our function never reaches an end (the 100% value) no matter the number of resources found it applies conveniently in our case.

 

An interesting point to notice is that for a progress factor of 2 the discovery of only 3 resources is enough to fill 87.5% of the progress bar. If the page has actually 100 resources the progress bar will take a long time showing a 99% value and the user experience in this case might not be exciting. In other words, if the factor is too small (like 2) the progress bar advances very quickly in the beginning and gets stuck in the end. If the factor is too big the progress bar might jump suddenly from 20% to 100%. Therefore, choosing an "appropriate" value for the progress factor is important. In our experiment we have observed good results for a progress factor of 10 (1/10th).

 

The percentageLeft parameter represents the size of the progress bar that can still be used to create slots as new resources are found. It starts with size 1 (100%) and progressively reduces in size but never gets to 0 (zero). Indeed, that's the idea, the progress bar is never fully filled out until all resources are loaded.

 

The resourcesTable is a map that relates resources to resource specific progress information as discussed next.

 

class BrowserFieldLoadProgressTracker {
 
     // The fraction used to split the remaining load amount in
// the progress bar after a new resource is found
// (e.g., 2=half, 3=one third, 5=one fifth)
     // Bar progress typically moves fast at the beginning and slows down
// progressively as we don't know how many resources still need to be loaded
     private float progressFactor;
    
     // The percentage value left until the progress bar is fully
// filled out (initial value=1 or 100%)
     private float percentageLeft;     
    
     // Stores info about the resources being loaded
// Map: Resource ---> ProgressTrackerEntry
     private Hashtable resourcesTable; 


 

The inner class ProgressTrackerEntry stores progress information about each resource, specifically, the total number of bytes for the resource (see variable bytesToRead), the number of bytes read so far (see variable bytesRead), and the slot size for that resource (see variable percentage). The table is used to compute the progress percentage value for the page being loaded.

 

Method updateBytesRead() is called whenever the BrowserField API signal that a new resource (or a new chunk of a resource) has been read. This method will typically update the bytesRead variable and eventually the bytesToRead variable as well.

 

static class ProgressTrackerEntry {

   ProgressTrackerEntry(int bytesRead, int bytesToRead, float percentage) {
       this.bytesRead = bytesRead;
       this.bytesToRead = bytesToRead;
       this.percentage = percentage;
   }

   int bytesRead;     // bytes read so far for this resource
   int bytesToRead;   // total number of bytes that need to be read
// for this resource
   float percentage;  // the amount (in percentage) this resource represents
// in the progress bar (e.g., 50%, 25%, 12.5%, 6.25%)
         
   public void updateBytesRead(int bytesRead) {
       bytesRead += bytesRead;
       if ( bytesRead > bytesToRead ) {  // this can happen when the final size
// of a resource cannot be anticipated
           bytesToRead = bytesRead;
       }
   }
}

 

Method updateProgress() is called from within the downloadProgress() event caught by the BrowserFieldScreen. This gives the progress tracker a chance to update its resource table and compute the progress value. Notice that each time updateProgress() is called either a slot is created for the new resource (entry == null) or the resource's slot is recovered and updated (entry.updateBytesRead()). Finally, the progress percentage value is computed based on the resource's table and returned (return getProgressPercentage())

 

 

public synchronized float updateProgress(ContentReadEvent event) {
        
   if ( resourcesTable == null ) {
       resourcesTable = new Hashtable();
   }
   
   Object resourceBeingLoaded = event.getSource();
   
   ProgressTrackerEntry entry =
(ProgressTrackerEntry)resourcesTable.get(resourceBeingLoaded);
   if ( entry == null ) {
       float progressPercentage = percentageLeft / progressFactor;
       percentageLeft -= progressPercentage;
       resourcesTable.put( resourceBeingLoaded, new
ProgressTrackerEntry(event.getItemsRead(), event.getItemsToRead(),
progressPercentage ) );

   }
   else {
       entry.updateBytesRead( event.getItemsRead() );
   }
return getProgressPercentage();
}

 

The progress percentage value is computed by traversing each resource in the resource's list and computing the fraction of each slot that should be added to form the final progress value. For each resource, we divide the number of bytes read by the total number of bytes for the resource (entry.bytesRead/entry.bytesToRead) and then multiply the result by the slot size ( ... * entry.percentage). The result indicates how much of the slot of each resource is used to compute the final percentage value for the progress bar. If the resource is fully loaded the entire slot is considered.

 

 

public synchronized float getProgressPercentage() {
    float percentage = 0f;
    for( Enumeration e = resourcesTable.elements() ; e.hasMoreElements() ; ) {
        ProgressTrackerEntry entry = (ProgressTrackerEntry)e.nextElement();
        percentage += ((entry.bytesRead/entry.bytesToRead)*entry.percentage);
    }        
    return percentage;
}   

 

 

We encourage the reader to have a look at the Mini-Browser solution's source code in the attachments and to eventually try it out.

 

Have fun with your progress tracker!

Contributors
Users Online
Currently online: 17 members 1,125 guests
Please welcome our newest community members: