Be persistent! – The Mobile Experiment – part III

Last post, I told you I was going to make the project list persistent, and that is exactly what I did. First, let me explain what does it mean.

The purpose of the Time Tracker (yes! it has a name!) project list is to help the user, allowing him to select one previous project from the list or type a new one. In addition, any new project entered should be available in the list next time, even between device resets. This is what “be persistent” means, or in a desktop language, we’re saving the content of the list, and loading it when the user activate the list again.

Reading the excellent book Beginning Blackberry Development from Apress,  I’ve found that Blackberry OS offers not one, but several ways to deal with persistent data. The selection of which one to use will basically depend on what kind of data you’re working with. The ones I’ve found suitable for my purpose are:

  • Persistent Object
  • File Connection JSR 75
  • SQLite (OS 5.0 or above)

Immediately, I disregarded SQLite due to the nature of the data I’m using here. In general, the project list will hold a very small amount of data. In a database term, it would be a single table with a single column/field in it. Therefore, I’ve found SQLite too much for just that. Going back to desktop development reference, this project list would be the kind of data I’d store in a INI file or a common text one. Depend on your project, the fact that it is supported only on devices with OS 5.0 or above is a decision factor, too.

Blackberry’s Developer website also offers a very good tutorial about the subject:  Storing Persistent Data (login may be required).

FileConnection is very like the standard desktop applications I/O functions. It this the way Blackberry OS will deal with different kinds of data, and with the ability to save them in the internal memory or memory card. Again, too much for a small project list. (Note to myself: this is probably the way to save the whole project tasks data)

So, as you can guess, I’m going to use the Persistent Object to accomplish what I’ve planned. All the job can be done with the PersistentStore and PersistentObject classes. It allows the application to persist data across reboots using the internal memory. Since this memory is shared with the actual applications, if is not recommended to keep large amount of data there.

The PersistentStore and PersistentObject are signed classes, so to use them in an actual device, it is necessary to buy code signed keys from http://us.blackberry.com/developers/javaappdev/codekeys.jsp. It costs US$20, but I’ve heard in a recent webinar that this price will be dropped to around 9 dollars.

The PersistentStore is a static class, accessible without having to explicitly instantiate it. The first step is create a long key, to be the unique ID for your persistent object. An easy way to create this number is use the option Convert String to Long, when selecting a word and right-clicking the mouse. Since this must be unique across all installed applications, it is a good practice to convert something very different, like your package name plus the class name.

So, the first step is declare this key and my PersistentObject inside my class, as global variables:


static final long KEY = 0xaadf7d062b2a3857L;
PersistentObject persistentProjectList;

In my constructor, I’ve  initialized the object using that key, using the getPersistentObject() method. The first time, there is no object to get, so the PersistentStore creates a new one. Taking the standard I/O functions for analogy, we could say that the call below opens a file or create a new one if it doesn’t exist, putting the file pointer into the persistentProjectList variable . This is why it is at the beginning of my constructor.

// Initialize persistent object
persistentProjectList = PersistentStore.getPersistentObject(KEY);

The next step is actually retrieve the data stored, but I didn’t tell you what format this data is after all. The PersistentObject can persist only objects (PersistentObject, you see!?). In other words, you cannot persist primitive types, like an int. In that case, if you really need to save only an integer, you can still use the Integer object instead.

The nice thing here is that you can create your own class, with the data organized inside it the way you want, and make it persistent. To do so, however, you have to implement the interface Persistable. You don’t have to do anything else, since this interface contains no methods.

In my application, I’m going to persist a list of projects –  basically an array of strings. I’ve found better use the object Vector, which is created with a certain size, but can grow automatically as needed, so I don’t need to handle it size, new allocations, and so on.

Again, in my class, I’ve declared an object of the type Vector:


static final long KEY = 0xaadf7d062b2a3857L;
PersistentObject persistentProjectList;
Vector projectList;

Now, comes the part where I’m going to load any previously saved data. Look at this code snippet from the beginning of my constructor:

public TimeTrackerScreen() {
  (....)
  // Initialize persistent object
  persistentProjectList = PersistentStore.getPersistentObject(KEY);
  // Get the content, if it exists
  if (persistentProjectList.getContents() == null) {
    // retrieve object list
    projectList = new Vector(10,1);
    persistentProjectList.setContents(projectList);
  }
  else {
      projectList = (Vector)persistentProjectList.getContents();
  }
  (...)

After initializing the persistentProjectList object, I call its getContents() method. As you can see, the code first check if it returns null, meaning that there is no data previously saved. There is the beauty of this solution: at this point I create my Vector object, and tell the PersistentObject that this newly created Vector will be the kind of object it’s going to be persistable. It doesn’t matter how many elements this array is going to have, the PersistentObject will always take care of save them all.

After the first time, the code will always execute the else statement, assigning the Vector to the projectList object.

Since I’m nice, and want more people coming read this blog, I’ll tell you one problem I had during the development: before deciding to use the Vector, I’d tried few other objects, and suddenly my application started to raise exceptions when executing that initialization part. The problem is that, since I’d saved one kind of data before, the application tried to use that kind of object as a Vector, making a mess. To avoid this problem, remember to clear the data in your simulator every time you change the object type (On Eclipse, choose Project->Blackberry->Clean simulator).

When the user clicks the button Start, the idea is to save any new project typed to be available next time the application is activated. To do that, you have to call the PersistentObject method commit(). Yes! There is no need to get the data from the Vector and add it to the PersistentObject before save it. The PersistentObject keeps your object reference during the application lifetime. In other words, you can change the Vector() the way you want without taking care of the way the PersistentObject sees it.

In today’s implementation, I had also to add the Vector content to the AutoCompleteField, which you can see reading the full source code below (I’ll be back to that part on my next post).


package com.mobilecreator.timetracker;

import java.util.Vector;

import net.rim.device.api.collection.util.BasicFilteredList;
import net.rim.device.api.i18n.SimpleDateFormat;
import net.rim.device.api.system.PersistentObject;
import net.rim.device.api.system.PersistentStore;
import net.rim.device.api.ui.DrawStyle;
import net.rim.device.api.ui.Field;
import net.rim.device.api.ui.FieldChangeListener;
import net.rim.device.api.ui.component.AutoCompleteField;
import net.rim.device.api.ui.component.ButtonField;
import net.rim.device.api.ui.component.Dialog;
import net.rim.device.api.ui.component.LabelField;
import net.rim.device.api.ui.container.MainScreen;
import net.rim.device.api.ui.component.EditField;
import net.rim.device.api.ui.component.DateField;

public class TimeTrackerScreen extends MainScreen implements FieldChangeListener {
	// unique key for the persistent object
	static final long KEY = 0xaadf7d062b2a3857L;
	PersistentObject persistentProjectList;
	Vector projectList;

	ButtonField buttonStart;
	LabelField dateLabel;
	AutoCompleteField projectListField;
	LabelField projectLabelField;
	BasicFilteredList filterList;
	EditField notesField;

	int buttonState = 0;

	public TimeTrackerScreen() {

		super();

		LabelField title = new LabelField("Tracker - mobilecreator.wordpress.com",
				LabelField.ELLIPSIS | LabelField.USE_ALL_WIDTH);
		setTitle(title);

		// Initialize persistent object
		persistentProjectList = PersistentStore.getPersistentObject(KEY);

		// Get the content, if it exists
		if (persistentProjectList.getContents() == null) {
			// retrieve object list
			projectList = new Vector(10,1);
			persistentProjectList.setContents(projectList);
		} else {
			projectList = (Vector)persistentProjectList.getContents();
		}

		// Search list for autocomplete field
		filterList = new BasicFilteredList();
		LabelField projectLabel = new LabelField("Project: ",LabelField.FIELD_VCENTER);
		add(projectLabel);

		if (projectList.size() > 0)
			populateProjectList();

		projectListField = new AutoCompleteField(filterList, AutoCompleteField.LIST_DROPDOWN);
		add(projectListField);

		// Don't add this, just create - it will be enabled during the
		// project
		projectLabelField = new LabelField("");

		LabelField notesLabel = new LabelField("Notes: ",LabelField.FIELD_VCENTER);
		add(notesLabel);
		notesField = new EditField(EditField.EDITABLE | EditField.USE_ALL_WIDTH);

		add(notesField);
		// last field that will display the date and time selected
	    dateLabel = new LabelField("Date/Time: ");
		add(dateLabel);

		DateField dateField = new DateField("", System.currentTimeMillis(), new SimpleDateFormat("dd/MM/yyyy hh:mm"), Field.FIELD_LEFT | DrawStyle.LEFT | Field.FOCUSABLE);
		this.add(dateField);

 		// Button
		buttonStart = new ButtonField("Start");
		buttonStart.setChangeListener(this);
		add(buttonStart);
	}

	public void fieldChanged(Field field, int context) {
		if(field == buttonStart) // if Delete button is clicked
        {
			Dialog d = new Dialog(Dialog.D_YES_NO, "Confirm ?", Dialog.NO, null,0);

			if(d.doModal() == Dialog.YES) {
				if (buttonState==0) {
					saveProjectList();
					buttonState = 1;
					buttonStart.setLabel("Stop");
					int x = projectListField.getIndex();
					insert(projectLabelField, x);
					projectLabelField.setText(projectListField.getEditField().getText());
					delete(projectListField);

					// Make other objects read-only
					notesField.setEditable(false);
					dateLabel.setEditable(false);
				} else {
					buttonState = 0;
					buttonStart.setLabel("Start");
					int x = projectLabelField.getIndex();
					projectListField.getEditField().clear(0);
					insert(projectListField, x);
					if (projectList.size() > 0)
						populateProjectList();

					delete(projectLabelField);

					// Make other objects editable
					notesField.setEditable(true);
					dateLabel.setEditable(true);
					projectListField.setFocus();
				}
			}
        }
	}

	private void saveProjectList()
	{
		// check if the selected string is already in the array
		String newProject = projectListField.getEditField().toString();
		if (projectList.contains(newProject) == false) {
			projectList.addElement(newProject);
		}
		persistentProjectList.commit();
	}

	private void populateProjectList()
	{
		String[] tempArray = new String[projectList.size()];
		projectList.copyInto(tempArray);
		filterList.clear();
		filterList.addDataSet(1,tempArray,"projects",BasicFilteredList.COMPARISON_IGNORE_CASE);
	}
}

(*) Sign the rss feed to be notified of new posts!

Advertisements
  1. #1 by Lee Mangold on November 13, 2010 - 10:19 am

    Have you had any problems with the PersistentStore on OS6, by chance? I’m trying to debug a problem that doesn’t occur on OS4.6 or 5.0:

    http://supportforums.blackberry.com/t5/Java-Development/Persistent-store-on-OS6/m-p/635622#M131978

    • #2 by mobilecreator on November 13, 2010 - 11:12 am

      I haven’t using OS 6.0 so far. I’ll do some tests and post at the BB forum. Thanks

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: