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

Reply
Developer
peter_strange
Posts: 19,608
Registered: ‎07-14-2008
My Device: Not Specified

Sample Please Wait or Progress Bar Code

[ Edited ]

I've watched many people struggle with the concept of running a network process, or any blocking process in the Background while still holding up the Ui, and displaying a progress indicator. 

 

There seem to be two common issues

a) you can't block the Event Thread so how do you get the UI processing to stall

b) You need to update the UI with notifications from the background Thread, so you need to have some sort of Observer Pattern or Interface.

 

To help explain both of these concepts, I have prepared the following sample UiApplication.

 

This is sample code designed to demonstrate how to overcome these two issues..  What I expect you to do is to create a sample project, add this code to it, compile the project and try the Application.  Then play with it, for example, add real networking code.  When you understand how this Application resolves the two common issues, then apply these principles to your own program.

 

Note that this is not simple, and believe me I tried to make it clear as I could.  But I also wanted to demonstrate a real situation, that you might actually see in your program.  The two don't sit well together, so I have tried to produce something useful rather than something easy to explain but completely useless in the real world. 

 

This is not production code, please do not copy this code, then complain that it does not work in your program. 

 

That said, I am very happy for you to point out bugs and make suggestions for improvement.  In fact I would encourage you to do this.  If we have a good sample of this, everyone will benefit.

 

There are 4 classes:

 

1) PleaseWaitDemo - is just the UiApplication and starts the PleaseWaitDemoScreen Screen, which is included in the source.  This is just the test 'rig' so that you run the Sample code. 

 

The popup screen is invoked from a FeildChange Listener - it could just as easily be in a Menu run method. 

 

2) PleaseWaitPopupScreen - this is the core of the processing, this starts the Background Thread as well as displaying the 'Please Wait Popup Screen with progress bar. 

 

There are three 'tricks' in here:

a) When the cancel button is pressed, this immediately tells the network thread to stop, and also then tells the Observer that the Network Thread has been stopped.  So while you might think that the Cancel button also should tidy up the popup screen, it does not need to, because the failure of the network thread has told it to do this anyway. 

b) The invalidate against the screen will cause the entire screen (the popup screen) to be repainted. 

c) Becuase each of the 'observer' methods is being invoked by the Background Thread, they need to use something like

UiApplication.getUiApplication().invokeLater

to 'move' the processing onto the Event Thread.  There are other ways of doing this, but I would recommend that you use invokeLater unless you have good reason to do otherwise.

 

3) NetworkThread - this is the Background Thread that is run.  You can change this to vary how long this Thread blocks, and the whether the Thread completes with an error or with a valid response.  This simulates a network (http) interaction, because that is what most people try to do.

 

You could argue that this code does not need the observer... methods because the places that call these methods could invoke the Observer directly.  However I would encourage you to leave these in.  In a more general implementaion of the Observer Interface, you would use a method like this, as it would have to do this processing for all the Observers (and there could be none). 

 

4) ObserverInterface - this is the interface the Background Thread uses to tell the listening Object (the PleaseWaitPopupScreen) that something has happened. This is not the 'standard Observer Pattern, it is a variation on this.  This Interface is specifically designed for a Network Background Thread, so I really should have called this NetworkObserverInterface. 

 

I hope the above, coupled with the comments in the code are enough.  If not, please ask for more explanation, I'm happy to give it.  As noted, I'll also be interested in improvements or bug fixes.  But please don't ask questions like "I used your code in my program and I'm getting ...."  If you have a problem with this code, please demonstrate the problem using just this code.  If you can't, then that would suggest to me that the problem is in your implementation and you should be able to fix that.  Hope that is OK. 

 

Enjoy

 

/**
 * Test UiApplication
 */

import net.rim.device.api.system.*;
import net.rim.device.api.ui.*;
import net.rim.device.api.ui.component.*;
import net.rim.device.api.ui.container.*;

public class PleaseWaitDemo extends UiApplication {

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

    //constructor
    public PleaseWaitDemo() {
        //Create a new screen for the application
        pushScreen(new PleaseWaitDemoScreen());
    }

}

class PleaseWaitDemoScreen extends MainScreen {

    PleaseWaitPopupScreen _waitScreen = null;
    LabelField _resultField = new LabelField("Result in here");

    public PleaseWaitDemoScreen() {
        this.setTitle("PleaseWaitDemo");
        ButtonField startButton = new ButtonField("Start", ButtonField.FIELD_HCENTER | ButtonField.CONSUME_CLICK);
        startButton.setChangeListener( new FieldChangeListener() {
            public void fieldChanged(Field field, int context) {
                _waitScreen = new PleaseWaitPopupScreen("Please wait", "Wating for test Thread", "dummy URL, not actually used");
                int result = _waitScreen.show();
                _resultField.setText("Result: " + Integer.toString(result));
            }
        });
        this.add(startButton);
        this.add(_resultField);
    }

}

 

 

import net.rim.device.api.ui.UiApplication;
import net.rim.device.api.ui.component.*;
import net.rim.device.api.ui.container.*;
import net.rim.device.api.ui.*;

/**
 * Wait for some Background processing to complete.
 * Meanwhile, display this popup screen and status updates as supplied.
 * Also allow the user to Cancel the Thread we are waiting on.
 */

public class PleaseWaitPopupScreen extends PopupScreen
                                   implements ObserverInterface {

    private String _title; // Title line for Popup

    private GaugeField _gaugeField = null; // Indicator to user that things are happening
    private ButtonField _cancelButton = null; // Button user can use to get out
    private LabelField _statusText = null;

    private NetworkThread _requestThread = null;
    private String _requestURL = null;
    private byte [] response;
    private int _returnCode = ObserverInterface.CANCELLED;

    public PleaseWaitPopupScreen(String title, String text, String requestURL) {
        super(new VerticalFieldManager());
        this.add(new LabelField(title, LabelField.FIELD_HCENTER));
        this.add(new SeparatorField());
        this.add(new RichTextField(text, Field.READONLY));
        _gaugeField = new GaugeField(null, 1, 100, 1, GaugeField.NO_TEXT);
        this.add(_gaugeField);
        this.add(new SeparatorField());
        _cancelButton = new ButtonField("Cancel", ButtonField.FIELD_HCENTER | ButtonField.CONSUME_CLICK);
        _cancelButton.setChangeListener( new FieldChangeListener() {
            public void fieldChanged(Field field, int context) {
                if ( _requestThread != null ) {
                    if ( _requestThread.isAlive() ) {
                        _requestThread.stop();
                        // This will send us a 'failure' notification
                    }
                } else {
                    // Something has gone really wrong?!
                    throw new RuntimeException("Oppsss");
                }
            }
        });
        this.add(_cancelButton);
        _cancelButton.setFocus();
        _statusText = new LabelField("Starting");
        this.add(_statusText);
        _requestURL = requestURL;
    }

    public int show() {
        _requestThread = new NetworkThread(_requestURL, this);
        _requestThread.start();
        UiApplication.getUiApplication().pushModalScreen(this);
        return _returnCode;
    }


    /**
     * This method is called by the Background Thread
     * So we need to gain access to the Event Thread to update our Ui.
     */
    public void processStatusUpdate(final int status, final String statusString) {
        UiApplication.getUiApplication().invokeLater(new Runnable() {
            public void run () {
                _statusText.setText(statusString);
                if ( status > 0 ) {
                    _gaugeField.setValue(status);
                }
                PleaseWaitPopupScreen.this.invalidate();
            }
        });
    }

    /**
     * This method is called by the Background Thread
     * So we need to gain access to the Event Thread to update our Ui.
     */
    public void processResponse(final byte [] responseBytes) {
        _returnCode = ObserverInterface.OK;
        UiApplication.getUiApplication().invokeLater(new Runnable() {
            public void run () {
                Dialog.alert("Response:\n" + new String(responseBytes));
                UiApplication.getUiApplication().popScreen(PleaseWaitPopupScreen.this);
            }
        });
    }
            
    /**
     * This method is called by the Background Thread
     * So we need to gain access to the Event Thread to update our Ui.
     */
    public void processError(int errorCode, final String errorMessage) {
        _returnCode = errorCode;
        UiApplication.getUiApplication().invokeLater(new Runnable() {
            public void run () {
                Dialog.alert("Error:!\n" + errorMessage);
                UiApplication.getUiApplication().popScreen(PleaseWaitPopupScreen.this);
            }
        });
    }

}

 

 

/**
 * NetworkThread
 * 
 * This is just a Thread which the Main UI must wait for.
 * 
 * In most applications this will be a networking Thread
 * so this sample has been designed to simulate that.
 * It is created with a URL and returns a byte array.
 * But this general 'pattern' applies anywhere the UI needs to wait.
 *
 */

import net.rim.device.api.ui.*;
import net.rim.device.api.ui.component.*;
import net.rim.device.api.ui.container.*;
import net.rim.device.api.system.*;
import javax.microedition.io.*;
import java.io.*;

public class NetworkThread extends Thread {

    private ObserverInterface _ourObserver;
    private String _targetURL;
    private boolean _stopRequest = false;

    public NetworkThread(String requestURL, ObserverInterface observer) {
        super();
        _targetURL = requestURL;
        _ourObserver = observer;
    }

    /**
     * stop is called if the processing should not continue.
     */
    public void stop() {
        // Tell our observer
        observerError(ObserverInterface.CANCELLED, "Cancelled by User");
        _stopRequest = true; // Will no longer tell Observer anything
        this.interrupt(); // Give our Thread a kick
    }

    private void observerStatusUpdate(final int status, final String statusString) {
        if ( !_stopRequest ) {
            _ourObserver.processStatusUpdate(status, statusString);
        }
    }

    private void observerError(int errorCode, String errorMessage) {
        if ( !_stopRequest ) {
            _ourObserver.processError(errorCode, errorMessage);
        }
    }

    private void observerResponse(byte [] reply) {
        if ( !_stopRequest ) {
            _ourObserver.processResponse(reply);
        }
    }

    /**
     * Process the long running or blocking operation in this Thread
     * Update the Observer as required using
     * - processStatus, whenever desired
     * and then one of:
     * - processError, if there was a problem
     * - processResponse, if the data was obtained OK
     */
    public void run () {

        // Following are just test variables, hopefully self eplanatory
        long stallTime = 10000; // Will stall for this time
        boolean willFail = false; // false - will finshed OK, true - will fail!
        long updateInterval = 1000; // Update Observer every second
        long startTime = System.currentTimeMillis();
        long finishTime = startTime + stallTime;
        long currentTime = startTime;

        observerStatusUpdate(1, "Started"); // Tell user we have started

        if ( willFail ) {
            // Fail 1/2 way through
            finishTime = startTime + stallTime / 2;
        }

        while ( !_stopRequest &&
                currentTime < finishTime ) {
            // THis just simulates the fact that we can update the Observer (Ui) from time to time
            try {
                Thread.sleep(updateInterval);
            } catch (Exception e) {
            }
            // Calculate a percentage to tel the Observer
            currentTime = System.currentTimeMillis();
            int percentageFinished = (int) (((currentTime - startTime) * 100l) / stallTime);
            percentageFinished = Math.min(percentageFinished, 99); // Just so we don't exceed guage value.
            observerStatusUpdate(percentageFinished, "Processing: " + Integer.toString(percentageFinished));
        }

        observerStatusUpdate(100, "Finished"); // Tell Observer we have finished

        //  Did we finish OK or badly
        if ( willFail ) {
            observerError(ObserverInterface.ERROR, "Failed");
        } else {
            observerResponse("Succeeded".getBytes());
        }

        // Make sure we do nothing else
        _stopRequest = true;
        _ourObserver = null;
    }

}

 

/*
 * ObserverInterface.java
 * 
 * Please do not think this is an approved implemenation of the Observer Pattern,
 * It's not.  it is just something I have made up.
 *
 */

public interface ObserverInterface {

    public void processStatusUpdate(int status, String statusString);
    // Observer can be notified by Observed as it is going along, with regular 
    // status updates
    // Could be used to pass messages back to be displayed, for example a sequence like:
    // finding Server, logging in, logged in, requested update, update received, logging out....
    // Could also be used as in this sample, to pass back a % complete

    public void processResponse(byte [] responseBytes);
    // If the processing is successful, response is passed to called using this.

    public void processError(int errorCode, String errorMessage);
    // If there is an error, an error indication is passed back using this
    public static int CANCELLED = -1;
    public static int ERROR = -2;
    public static int OK = 0;
    // These are all ,= 0.  We grubbily also use the 

}

 

 

 

Please use plain text.
Developer
GlenBirkbeck
Posts: 54
Registered: ‎10-23-2008
My Device: Not Specified

Re: Sample Please Wait or Progress Bar Code

Excellent stuff Peter, thanks for sharing. I've just implemented a progress bar in my application, not as cleanly as you do here so I think I will revisit it.

Please use plain text.
Developer
rcmaniac25
Posts: 1,805
Registered: ‎04-28-2009
My Device: Z10 (STL100-4)-10.2.1.3253, Z10 (STL100-3)-10.3.1.634 Dev OS, Z30 (STA100-5)-10.3.1.634 Dev OS, Passport (SQW100-1)-10.3.0.1418, PlayBook (16GB)-2.1.0.1917

Re: Sample Please Wait or Progress Bar Code

Great job Peter, this definitely demonstrates how to make a Please Wait/Progress Bar pop-up. I couldn't find any errors just by looking at it so good job.

---Spends time in #blackberrydev on freenode (IRC)----
Three simple rules:
1. Please use the search bar before making new posts.
2. "Like" posts that you find helpful.
3. If a solution has been found for your post, mark it as solved.
--I code too much. Well, too bad.
Please use plain text.
Developer
peter_strange
Posts: 19,608
Registered: ‎07-14-2008
My Device: Not Specified

Re: Sample Please Wait or Progress Bar Code

Thank you guys.

 

I've updated the text to expland on some of the tricker points in the processing.  Hope that hasn't made it more confusing. 

Please use plain text.
Developer
klerisson
Posts: 78
Registered: ‎12-03-2009
My Device: Not Specified

Re: Sample Please Wait or Progress Bar Code

Hey Peter! Congratulations for the great explanation.

 

I usually management my important samples and production code using different workspaces, for sure I have to create another workspace called "Peter's made up"  :smileyvery-happy:

 

Anyway, on the ObserverInface file you wrote that is not an approved implementation of the Observer Pattern. So, how could you implement the same using the observer pattern restrictedly?

 

Thank you so much!

--
Feel free to press the kudos button on the left side to thank the user that helped you.
Please mark posts as solved if you found a solution.
Please use plain text.
Developer
peter_strange
Posts: 19,608
Registered: ‎07-14-2008
My Device: Not Specified

Re: Sample Please Wait or Progress Bar Code

"how could you implement the same using the observer pattern restrictedly?"

 

Good question.

 

I'm not well learned on this, so I think people who more actively use these sorts of things would be better able to answer this. 

 

However for me, there are these differences:

 

1)  ObserverInterface usually involves only 1 method, typically update(), so the Observed (in this case the Network Thread) will only ever call update().  That makes the interface very general.  However in this case I know that I'm only ever going to be called back in three cases, [(1) progress report, (2) completed and failed, (3) completed OK], so I have three call backs. 

 

2) There is a debate as to whether you push the data to the Observer (as I do) or tell the Observer that something has changed and that they should pull the data that they want.  Again, because I know what data I want in each case, I push the data.

 

3) Not really an interface issue, but related.  The Observed (Network Thread) only supports one Observer, so there is no 'register Observer' method. 

 

4) The update method usually indicates the object being Observed, so that the Observer can use the same update() method for multiple Observed objects.  I know that the only object that my Observer is watching is the Network Thread, so I don't need to supply that.

 

Sorry I'm not going to convert this to use the normal Observer/ObserverInterface.  It doesn't exist in J2ME and so you have to create your own anyway.  I have chosen to create a specific implementation that suits my needs, and would not change it to use the true Observer pattern.   Hope that is OK. 

 

If you would like to try re-writing this to use a normal ObserverInterface then please post it.  But I suspect it will be longer and more complicated!

Please use plain text.
Contributor
cyork2
Posts: 34
Registered: ‎02-28-2010
My Device: 8800

Re: Sample Please Wait or Progress Bar Code

I know this is an old thread... but THANK YOU PETER!

 

If I hadn't read through your example I would never have realized my thread was blocking because I was trying to update the UI from the background thread without even thinking about it (by doing a set text).

 

You are one of a number of helpful people on this board that make it possible to do this by myself.. thanks to you and the many people like you who are providing help.

Please use plain text.
Contributor
jasbeern
Posts: 17
Registered: ‎06-17-2010
My Device: C

Re: Sample Please Wait or Progress Bar Code

Good explanation.

Thanks for this post. Kudos to you.

Please use plain text.
Developer
peter_strange
Posts: 19,608
Registered: ‎07-14-2008
My Device: Not Specified

Re: Sample Please Wait or Progress Bar Code

There is updated and better explained similar code in the KB now, see here:

 

http://supportforums.blackberry.com/t5/Java-Development/Sample-quot-Please-Wait-quot-screen-part-1/t...

Please use plain text.
Developer
rizinbox
Posts: 139
Registered: ‎10-24-2010
My Device: BlackBerry 10 Dev Alpha

Re: Sample Please Wait or Progress Bar Code

Thanks Peter,

 

But can you explain how this code I can use for BlueTooth searching process..

 

And in progreess will show how many devices found etc.....

--
please mark posts as solved if you found a solution.
press the like button on the right side to thank the user that helped you.
------------------------------------------------------------
Riz
Please use plain text.