Implement Simplified Observer Pattern to Avoid BlackBerry UI Lockup
- August 7th, 2011
- Posted in Tutorials
- By admin
- Write comment
One of the biggest problems I find with BlackBerry applications is that many non-thread safe operations are done on the event thread. Since the main event thread is the thread which supports the UI operations, it ends up locking up the UI and removing the heartbeat for the user. Keep in mind that these classes are ONLY for operations that are heavy enough that the user will notice. They can be used as is while only needing to modify the thread “run()” function. Any extremely simple operation that may only lock up the UI for 1-2 seconds if done on the event thread, should be simply done in a runnable and not waste effort with the classes below.
Below are a few classes I use to eliminate UI lag for any web requests, networking and, heavy processing.
Classes explained below:
Thread that does the work:
Popup to show the user the thread is working:
Interfaces:
Usage for the above classes is as follows:
1 2 3 4 5 6 7 | ProcessingPopup popupScreen = new ProcessingPopup("Calendar", "Adding...", "Cancel Download", "Started"); popupScreen.setThread(new ProcessingThread(popupScreen)); popupScreen.show(); byte[] response = popupScreen.getThreadResponse(); if (response.length > 0){ Dialog.alert(new String(response)); } |
Note that the ProcessingPopup.java can really be anything. You can have any UI element, not just a dialog. For the purpose of this tutorial however, it will be a dialog.
Below is the ProcessingPopup that implements the interface included above (but not shown here).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | package ca.dftr.lib.threading; import ca.dftr.lib.threading.ObservedThread; import ca.dftr.lib.threading.iObserver; import net.rim.device.api.ui.Field; import net.rim.device.api.ui.FieldChangeListener; import net.rim.device.api.ui.UiApplication; import net.rim.device.api.ui.component.*; import net.rim.device.api.ui.container.PopupScreen; import net.rim.device.api.ui.container.VerticalFieldManager; /** * @author deforbes <br> * A class that can handle any thread that extends ObservedThread to allow automatic application heartbeat/user feedback. * * Any UI component that implements iObserver will be able to automatically handle any class that is derived from ObservedThread * and give user UI feedback during any multi-threaded execution. */ public class ProcessingPopup extends PopupScreen implements iObserver { private GaugeField _progressBar = null; private ButtonField _cancelButton = null; private LabelField _statusText = null; // What this thread is currently doing. Will be displayed to user. private byte[] _threadResponse = null; private ObservedThread _observedThread = null; private int _returnCode = iObserver.OK; // By default lets assume that the observer is ok since nothing is observed on instantiation. /** * Constructor for creating a popup for showing thread activity * @param title Title of the popup dialog * @param text Explanitory text * @param cancelButtonText Information on the cancel button. Can be "cancel" or "stop download" etc. * @param initialStatusText Status is what the thread is currently doing. "Beginning", "Downloading" etc. */ public ProcessingPopup(String title, String text, String cancelButtonText, String initialStatusText) { super(new VerticalFieldManager()); this.add(new LabelField(title, LabelField.FIELD_HCENTER)); this.add(new SeparatorField()); this.add(new RichTextField(text, Field.READONLY)); _progressBar = new GaugeField(null, 1, 100, 1, GaugeField.NO_TEXT); this.add(_progressBar); this.add(new SeparatorField()); _cancelButton = new ButtonField(cancelButtonText, ButtonField.FIELD_HCENTER | ButtonField.CONSUME_CLICK); _cancelButton.setChangeListener(setCancelFunction()); this.add(_cancelButton); _cancelButton.setFocus(); _statusText = new LabelField(initialStatusText); this.add(_statusText); } /** * Creates a change listener for what to do when cancel button is clicked * @return FieldChangeListener Function for what to do when the cancel field changes. */ private FieldChangeListener setCancelFunction() { return new FieldChangeListener() { public void fieldChanged(Field field, int context) { if ( _observedThread != null ) { if ( _observedThread.isAlive() ) { _observedThread.stop(); // Sends a 'failure' notification i.e. processError() will be called with user cancelled return code } } else { throw new RuntimeException("Thread to start is Null"); } } }; } /** * set the associated thread. * @param threadToRun The associated thread that will update this ui and do the work. */ public void setThread(ObservedThread threadToRun){ _observedThread = threadToRun; } /** * Begin the thread and show the modal dialog * @return The success code of the thread from one of iObserver.CANCELLED/OK/ERROR */ public int show() { _observedThread.start(); UiApplication.getUiApplication().pushModalScreen(this); return _returnCode; } /** * Update progress bar and labels * @param status Update progress bar with progress between 0 and 100 * @param statusString Explaination of current work */ public void processStatusUpdate(final int status, final String statusString) { UiApplication.getUiApplication().invokeLater(new Runnable() { public void run () { _statusText.setText(statusString); if ( status > 0 && status <= 100) { _progressBar.setValue(status); } ProcessingPopup.this.invalidate(); //UI repaint. } }); } /** * Used to retreive any result of background processing. * i.e. This is where you would retreive the actual file or save location of a * download thread. */ public byte[] getThreadResponse(){ return _threadResponse; } /** * Process response from the thread this class is responsible for * and remove the popup. This is called by the thread when execution is complete. */ public void processResponse(final byte [] responseBytes) { _returnCode = iObserver.OK; _threadResponse = responseBytes; UiApplication.getUiApplication().invokeLater(new Runnable() { public void run () { UiApplication.getUiApplication().popScreen(ProcessingPopup.this); } }); } /** * Process a failing of the background thread. Thread stops immediately after this function call. * Kill this screen and alerts user. Logging is handled by ObservedThread. */ 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(ProcessingPopup.this); } }); } } |
And finally an example of a thread. To be used in the above class, it must implement ObservedThread which is also included above. It is not in the example since the guts of the explanation are in the classes that actually implement the class templates, and not the templates themselves.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | package ca.dftr.calendar.threads; import ca.dftr.lib.threading.ObservedThread; import ca.dftr.lib.threading.iObserver; /** * @author deforbes * A thread meant to be used with an iObserver. Completes operation while notifying user. */ public class ProcessingThread extends ObservedThread { private boolean _isStopped = false; private iObserver _observer = null; //private boolean _isForegroundThread = false; public ProcessingThread(iObserver observer) { super(observer); _observer = observer; /*Application application = Application.getApplication(); if ( application instanceof UiApplication ) { _isForegroundThread = true; } else { // Not created by UiApplication - don't use it when responding _isForegroundThread = false; }*/ } /** * User interrupt */ public void stop() { observerError(iObserver.CANCELLED, "Cancelled by User"); _isStopped = true; // Will no longer tell Observer anything //Clean up any saved files try{}Catch(Exception e){} this.interrupt(); // Stop thread } protected void observerStatusUpdate(final int status, final String statusString) { if ( !_isStopped ) { _observer.processStatusUpdate(status, statusString); } } protected void observerError(int errorCode, String errorMessage) { if ( !_isStopped ) { _observer.processError(errorCode, errorMessage); } } protected void observerResponse(byte [] reply) { if ( !_isStopped ) { _observer.processResponse(reply); } } public void run() { try{ observerStatusUpdate(1, "Started"); Thread.sleep(2000); observerStatusUpdate(5, "Waiting..."); //Got Response. Tell user Thread.sleep(2000); observerStatusUpdate(10, "Waiting Longer"); Thread.sleep(2000); observerStatusUpdate(30, "Waiting Still"); Thread.sleep(2000); observerStatusUpdate(60, "Waiting!"); Thread.sleep(2000); observerStatusUpdate(80, "lalala"); Thread.sleep(2000); //This I know is stupid code, Just showing that you can throw errors to kill thread during execution. //if (somethingScrewedUpOMG){ //observerError(iObserver.ERROR, "Something screwed up, or doesn't exist etc"); //} //if (_isForegroundThread){ //can do stuff in the UI without having to grab it or notify observer //} observerStatusUpdate(100, "Finished!"); Thread.sleep(2000); //This will be available by "getThreadResponse()" on the popup dialog object; observerResponse("Completed this thread execution!".getBytes()); } catch(Exception e){ observerError(iObserver.ERROR, "Failed"); } this._observer = null; this._isStopped = true; } } |
how to use the above methods to call webservice and to show the processing status. i have added the below code which im using to call the web service
I’m glad you liked the methods, but I don’t know as to whether I understand what the code below has to do with it.
in the above mentioned code. we are using two threads one to call the web service method and the another to show to some processing status.show(“processing..”). instead of that i want to use your code.. this is the web service call in my code chk_Login = WebserviceUtil.checkLogin(userName,password,deviceId); how to use your code to call the web service
You need to download all the code that is linked in the very first section:
ProcessingThread.java
ProcessingPopup.java
iObserver.java
ObservedThread.java
The one that you modify is the “ProcessingThread”. All you need to change is the “Run” method. You put all the steps of what you want to do in there and call observerStatusUpdate in between each with a percentage you want the progress bar to display. The problem with your code however is you have only one function call. There would only be one call to your webserviceUtil so really you only have two spots you could update the UI.
You have two choices really:
1) take out some of your code from your web service util, so that you have multiple things happening in the run function and you can call observerStatusUpdate multiple times OR
2) use a different class like an animated gif such as an ajax loader pop up to show the app has not stalled.
public LoginResponse checkLoginThread(final String userName,final String password,final String deviceId)
{
ApplicationContext.exception = null;
new Thread(new Runnable(){
public void run()
{
try{
chk_Login = WebserviceUtil.checkLogin(userName,password,deviceId);
}catch(Exception e){
ApplicationContext.exception = e;
ApplicationContext.start = false;
}finally{
ApplicationContext.start = false;
}
}
}).start();
Utility.showStatus(Constants.MSG_AUTHENTICATION );
return chk_Login;
}
how to use ur code to call the webservice and while calling it should show some processing.. can u guide me how to use ur code..
While loading the application as explained in the first box i’m getting an uncaught exception: no application instance
Does your application properly extend UI application? the only lines that could throw that error is:
UiApplication.getUiApplication()
I’ll immediately seize your rss as I can’t find your email subscription hyperlink or newsletter service. Do you’ve any?
Kindly allow me recognise in order that I could subscribe.
Thanks.