Archive for the ‘Tutorials’ Category

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

Creating a BlackBerry License Screen Agreement Popup

All the native BlackBerry applications have a license agreement that pops up looking like this. With the class I am including below it should be very easy to create your own. Resource files should be used. It is simple to create them by right clicking on your “res” folder and selecting “BlackBerry Resource File” from the menu. This will also create the class in “.locale_interfaces” used to access those resources. After adding the required keys you should change the license agreement value to “Convert to Multiple Values” and then add the agreement within the popup. The file that the below usage example uses is:

LicensePopup.java

I will include the usage first as it is a little more complex. The popup must be called prior to entering the main thread, or event dispatcher. The way this is done is as follows:

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
protected static ResourceBundle _resources = ResourceBundle.getBundle(BBiCalResource.BUNDLE_ID, BBiCalResource.BUNDLE_NAME);
/**
* Is synonymous with the main function. I separated it and the main function calls run().
*/
public void run(){
	invokeLater(new Runnable() {
		public void run() {
			LicensePopup popupDialog = new LicensePopup(_resources.getStringArray(BBiCalResource.ABOUT_LICENSE),
					_resources.getString(BBiCalResource.MENUITEM_ACCEPT), _resources.getString(BBiCalResource.MENUITEM_DECLINE), 6);
			pushGlobalScreen(popupDialog, 1, UiEngine.GLOBAL_MODAL);
 
			if(!popupDialog.isLicenseAccepted()) {
				System.exit(0);
			}
			else {
				//TODO persistant store that the license was accepted.
				requestForeground();
				pushScreen( new HomeScreen() );
			}
		}
        });
 
	enterEventDispatcher();
}

Below is the license agreement class:

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
package ca.dftr.lib.ui.components;
 
import net.rim.device.api.ui.Field;
import net.rim.device.api.ui.FieldChangeListener;
import net.rim.device.api.ui.Font;
import net.rim.device.api.ui.Manager;
import net.rim.device.api.ui.Ui;
import net.rim.device.api.ui.component.ButtonField;
import net.rim.device.api.ui.component.RichTextField;
import net.rim.device.api.ui.component.SeparatorField;
import net.rim.device.api.ui.container.PopupScreen;
import net.rim.device.api.ui.container.VerticalFieldManager;
 
/**
 * Pop up screen that looks like the default BlackBerry License screen
 * @author deforbes
 */
public class LicensePopup extends PopupScreen {
    private ButtonField _btnOkay;
    private ButtonField _btnCancel;
    private RichTextField _licenseTextField;
    private boolean _accepted;
 
    /**
     * Create the License Popup
     * @param licenseText The actual License as a string array (each line is a part of the array). Best to use a resource array
     * @param acceptBtnText Text on the accept button
     * @param declineBtnText Text on the decline button
     * @param fontSize Size of the font in the popup. (RIM uses 6)
     */
    public LicensePopup(String[] licenseText, String acceptBtnText, String declineBtnText, int fontSize) {
        super(new VerticalFieldManager(Manager.VERTICAL_SCROLL | Manager.VERTICAL_SCROLLBAR));
 
        if (fontSize <= 0){
        	fontSize = 6;
        }
        Font font = Font.getDefault().derive(Font.PLAIN, fontSize, Ui.UNITS_pt);
 
        _licenseTextField = new RichTextField(RichTextField.READONLY);
        for(int i=0; i<licenseText.length; i++) {
        	_licenseTextField.insert(licenseText[i]);
            if(i < licenseText.length - 1) {
            	_licenseTextField.insert("\n");
            }
        }
        _licenseTextField.setFont(font);
        _licenseTextField.setEditable(false);
 
        _btnOkay = new ButtonField(acceptBtnText, ButtonField.CONSUME_CLICK | Field.FIELD_HCENTER);
        _btnOkay.setChangeListener(new FieldChangeListener() {
            public void fieldChanged(Field field, int context) {
            	setAccepted(true);
            }});
        _btnCancel = new ButtonField(declineBtnText, ButtonField.CONSUME_CLICK | Field.FIELD_HCENTER);
        _btnCancel.setChangeListener(new FieldChangeListener() {
            public void fieldChanged(Field field, int context) {
            	setAccepted(false);
            }});
 
        add(_licenseTextField);
        add(new SeparatorField());
        add(_btnOkay);
        add(_btnCancel);
    }
 
    /**
     * Set whether the user clicked accept or decline
     * @param accepted Whether user accepted the license
     */
    private void setAccepted(boolean accepted) {
        _accepted = accepted;
        this.close();
    }
 
    /**
     * @return Whether or not the user accepted the license agreement
     */
    public boolean isLicenseAccepted() {
        return _accepted;
    }
}

Getting Started: BlackBerry JRE installation for different OS support

This information is outdated. Take it as you will.
There are a few problems with figuring out how to do things within the world of BlackBerry Development. These getting started posts will try to alleviate some of the problems caused by such things as:
  1. The getting started links after creating a BlackBerry Developer Zone login return 404 errors.
  2. Downloading and installing the correct JRE components often fails (with older JRE’s) and requires a login to an account for swdownloads.blackberry.com.
  3. Very difficult to figure out how to automatically compile for multiple platforms
After a few hours of searching I happened upon this link. Prior to this there appears to have been nowhere to find this information except through word of mouth. Since many BlackBerry.com websites often get lost in the void, a summary:
  1. Download and install the latest BlackBerry plug-in for Eclipse. Note that this should install a separate Eclipse: do not try to combine the installation that you’ve been working on for your Android application. Use two separate Eclipse instances for BB or Droid.
  2. Open it up after choosing a workspace and click the “Help” button on the top toolbar. Go to “Install new software” and add a new site named Blackberry Java Plug-in Update Site with location of http://www.blackberry.com/go/eclipseUpdate/3.6/java .
  3. Expand the “BlackBerry Java Plug-in Category and choose the SDK’s. Remember, the lowest version you can download (4.5.0.xx) is the lowest SDK you can use for your general library. So check the API reference to make sure everything that you need is there. This is due to the way that development for multiple OS’ work as is explained here  by Derek Konigsberg who created LogicMail.
  4. Optionally change the color scheme to what you are more comfortable with. If you do not like the default you can change the Eclipse background and colors by going to the same “Install new software” but using http://eclipse-color-theme.github.com/update/ as the site.
What that page does not tell you is that you will have a heck of a time downloading any SDK below OS7. After a few tries I found I had to do them individually, restarting eclipse after each install, or it would fail when asking for my credentials. I also found I had to create a new, fake, developer zone login. This login had to be confirmed (by checking email and clicking link) and had to have my information second checked and confirmed, which happens after your first login. After that, I was able to download the SDKs one by one.
My recommendation is to support only OS5 and up. This is due to the fact that majority of the devices that could be upgraded were. This is also due to the fact that it is a lot easier to do networking tasks (*todo, link to networking post) as there was an overhaul as of OS5.  It is also likely that the BlackBerry application player for QNX will better support 5 and up. However, if your device is a pearl flip for example, you are left with no choice but to support 4.6.1+.
The best information for a just starting BlackBerry java developer would be also from Derek at the site linked above. So that it is not lost, it is also here
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.