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:

ProcessingThread.java

Popup to show the user the thread is working:

ProcessingPopup.java

Interfaces:

iObserver.java

ObservedThread.java

 

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;
	}
}

Related Posts: