Archive for August, 2011

A Basic Error Logging Class

This should help make BlackBerry application error logging easier. To see your event-log you can type Alt+LGLG on your home screen. You can also use javaloader.exe (comes in the bin folder of the JDE) to export the log to a text file.

Javaloader -u eventlog > textfile.txt

Using the class below, you must change the unique bundle ID and name to match your application. This can be done for you when you add any new resource file to your project. Within the class used to access the resource strings will be BUNDLE_ID and BUNDLE_NAME. Replace those values in the class below.

Download the class below:

Logger.java

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
package ca.dftr.lib.error;
 
/*
 * Logger.java
 */
import net.rim.device.api.system.EventLogger;
import ca.dftr.lib.resources.libResource;
 
public class Logger {
 
	public static int LEVEL_INFO = EventLogger.INFORMATION;
	public static int LEVEL_WARNING = EventLogger.WARNING;
	public static int LEVEL_ERROR = EventLogger.ERROR;
	public static int LEVEL_SEVERE_ERROR = EventLogger.SEVERE_ERROR;
 
	public static String LOG_PREFIX_INFO = "Info: ";
	public static String LOG_PREFIX_WARN = "Warning: ";
	public static String LOG_PREFIX_ERROR = "Error: ";
	public static String LOG_PREFIX_SEVERE = "Severe Error: ";
 
	//Must put in your unique bundle ID and name.
	static {
		EventLogger.register(libResource.BUNDLE_ID, libResource.BUNDLE_NAME, EventLogger.VIEWER_STRING);
	}
 
	public static void logEventInfo(String eventData) {
		Logger.logEvent(Logger.LEVEL_INFO, Logger.LOG_PREFIX_INFO + eventData);
	}
 
	public static void logEventWarn(String eventData) {
		Logger.logEvent(Logger.LEVEL_WARNING, Logger.LOG_PREFIX_WARN + eventData);
	}
 
	public static void logEventError(String eventData) {
		Logger.logEvent(Logger.LEVEL_ERROR, Logger.LOG_PREFIX_ERROR + eventData);
	}
 
	public static void logEventSevere(String eventData) {
		Logger.logEvent(Logger.LEVEL_SEVERE_ERROR, Logger.LOG_PREFIX_SEVERE + eventData);
	}
 
	/**
	 * Log Event.
	 * Note: The event will only end up in the log depending on the
	 * EventLogViewer min log level. Accessed via alt+LGLG on home screen
	 */
	public static void logEvent(int level, String eventData) {
		EventLogger.logEvent(libResource.BUNDLE_ID, eventData.getBytes(), level);
		System.out.println(eventData);
	}
}

Change Color of Font in BlackBerry LabelField

In a previous post I linked the ColoredLabelField.java class. This class has replaced all of my LabelFields. I forget where I got the details from (probably stack overflow) though its been changed since then. Either way, this is how you change font color of a UI element on BlackBerry. Figured this is a pretty basic thing that I may as well make easier to find.

ColoredLabelField.java

 

 

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
package ca.dftr.calendar.ui.components;
 
import net.rim.device.api.ui.Graphics;
import net.rim.device.api.ui.component.LabelField;
 
/**
 * Creates a label field with a custom font color
 * @author deforbes, ...
 */
public class ColoredLabelField extends LabelField {
 
	/**
	 * ColoredLabelField constructor
	 * @param text Text to be contained within the field
	 * @param style LabelField modifiers (i.e. ColoredLabelField.FIELD_LEFT | ColoredLabelField.HIGHLIGHT_SELECT)
	 */
	public ColoredLabelField(String text, long style) {
		super(text, style); 
	}
 
	/**
	 * ColoredLabelField color constructor
	 * @param text Text to be contained within the field
	 * @param style LabelField modifiers (i.e. ColoredLabelField.FIELD_LEFT | ColoredLabelField.HIGHLIGHT_SELECT)
	 * @param color Font color (i.e. Color.BLACK)
	 */
	public ColoredLabelField(String text, long style, int color) {
		super(text, style);
		_fontColor = color;
	}
	private int _fontColor = -1;
 
	/**
	 * Set the LabelFields font color
	 * @param fontColor Font's Color
	 */
	public void setFontColor(int fontColor) {
		_fontColor = fontColor;
	}
 
	/**
	 * Get Font's Color
	 * @return Color bit.
	 */
	public int getFontColor(){
		return _fontColor;
	}
 
	protected void paint(Graphics graphics) {
		if (-1 != _fontColor)
			graphics.setColor(_fontColor);
		super.paint(graphics);
	}
}

Implement Simplified Observer Pattern to Avoid BlackBerry UI Lockup

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

INFORMATION

I do not adhere to a schedule. I post when I have something to say. I'm a programmer who in my spare time enjoys contemplating the meaning of life, the universe and, everything. So there will be code as well as random little stories, essays and, musing about whatever interests me at the moment.