Developers
Local Navigation
Michael Clewley, Editor
Everybody loves a good flick, and there's nothing better than watching your favourite movie over and over again in the comfort of your own home. Most people have their own stockpile of favorite movies for their own personal enjoyment. How many times have you been in a conversation with someone and they ask you “oh, so what movies do you have?”. You sit there, pull out the fingers and toes, start counting and can't even get past the first hand. You've forgotten your favourite collection! Don't worry; I've been there with you, with that same dumbfounded and disappointing look. So there's no better idea for an application than a movie-tracking database. It's a good way to learn how to create a basic application, it’s fun, and it’s something you're sure to use. By the end, you should have a really cool application that includes components you can use in future applications.
Now you ask yourself, where do I begin? Well the name of the game is tracking movies, so what's the first thing that comes to mind? Here are a few things that came to my mind:
- Title
- Studio
- Format (DVD/VHS)
- Did I lend it to a friend?
- If I lent it, to who?
I think those are the key pieces of information. But thinking about it more there were some other ideas. Who knows if they'll make it in the final application though! They include:
- length of movie
- comments / notes
- year
I'm sure the list could go on and on. Each person has different needs from an application. At some point you need to stop the daydreaming and make a decision, or you'll never get anything done!
Now that we've given our data model some thought, let's turn the ideas into actual code.
Start off by doing any necessary imports and then the class declaration. We know that this movie data is going to be store on the device. Make sure to import the necessary class.
package com.bbdj.samples.breel;
import net.rim.device.api.util.Persistable;
final public class BReelMovieElement implements Persistable {
The only other piece of information to note is that we are going to make the class final because its not going to be extended and it allows the compiler to optimize the class to create smaller code.
Now we take the ideas of the data and create variables for our class.
//variables private byte _lentTo[]; private byte _movieTitle[]; private byte _productionCompany[]; private int _genre; private int _format; private boolean _lentOut; //false
Let’s examine what happened in the code above. Each piece of information has been analyzed and turned into a primitive data type that best matches the functionality. As a personal preference, I like to group variables according to type. While some prefer to group alphabetically, it makes no difference. Data that is going to be plain text has been defined as byte arrays because it's more efficient to convert a string to a byte array and store the array than it is to store a String object. The format will be a number that represents the video format. And lastly, a boolean value is being used to represent whether the movie has been lent out.
The next piece is the constructor, but we are not actually initializing anything here so we'll omit it for now. It should be noted that the variables that we have defined at the class level are automatically initialized to their default values.
Moving along, we need ways to set the data and also extract the current values. In this application there are only five data types, so we use the set and get methods for each variable. If this class had many different variables then we might want to use an alternative method of retrieving the information, such as getInt(), getString(), getBoolen() methods.
/**
* @return the format of the movie
*/
/* package */ int getFormat() {
return _format;
}
/**
* @return True if lent out, false otherwise
*/
/* package */ boolean getLentOut() {
return _lentOut;
}
/**
* @return the genre of the movie
*/
/* package */ int getGenre() {
return _genre;
}
/**
* @return name of the person the movie was lent to
*/
/* package */ String getLentTo() {
if (_lentTo == null || _lentTo.length == 0) {
return "";
}
return new String(_lentTo);
}
/**
* @return title of the movie
*/
/* package */ String getMovieTitle() {
if (_movieTitle == null || _movieTitle.length == 0) {
return "";
}
return new String(_movieTitle);
}
/**
* @return production/film company name
*/
/* package */ String getProdCompany() {
if (_productionCompany == null || _productionCompany.length == 0) {
return "";
}
return new String(_productionCompany);
}
We now have a way of retrieving the data from the instance of the class. Everything shown above is pretty straightforward, but there might be some things that are not familiar to you. At the start of each method I have placed a comment /* package */. This means that the method has default permissions access and I just wanted to note that. The system does some optimization if the method modifier is omitted, and allows for a smaller file size for the application when completed. Lastly, the byte arrays values are all converted to a String object before being returned, with a check put in place to ensure that we don't return a null.
/**
* @param value - number assigned to the format
*/
/* package */ void setFormat(int value) {
_format = value;
}
/**
* If the value is true and set it false it will also
* clear the lentTo field.
* @param value true if lent out, false otherwise
*/
/* package */ void setLentOut(boolean value) {
_lentOut = value;
if (!_lentOut) {
setlentTo("");
}
}
/**
* @param value the genre of the movie
*/
/* package */ void setGenre(int value) {
_genre = value;
}
/**
* @param data name of the person the movie is lent to
*/
/* package */ void setlentTo(String data) {
_lentTo = data.getBytes();
}
/**
* @param data movie title
*/
/* package */ void setMovieTitle(String data) {
_movieTitle = data.getBytes();
}
/**
* @param data production/film company
*/
/* package */ void setProdCompany(String data) {
_productionCompany = data.getBytes();
}
With the ability to set the data in the object, our BReelMovieElement is now complete. The only note on the above methods would be those methods that are receiving String objects. From within the method we are converting the String object to a byte array using the getBytes() method.
With our element class now ready to roll, we need to a way to track a list of all the different movies. This list will implement the List interface, read and save the data from persistence, and sort the list as well. Let's dive in.
package com.bbdj.samples.breel;
import java.util.Vector;
import net.rim.device.api.collection.List;
import net.rim.device.api.system.*;
import net.rim.device.api.ui.Graphics;
import net.rim.device.api.ui.component.ListFieldCallback;
import net.rim.device.api.ui.component.ListField;
/**
*
*/
class BReelMovieList implements List, ListFieldCallback {
/**
* The ID used to store the data in persistence
*/
//com.bbdj.samples.breel.BReelMovieList
private static final long B_REEL_MOVIE_LIST = 0x2713ab1c1a7c4c15L;
/**
* Comparator for the movie elements
*/
private BReelMovieElementComparator _comparator;
/**
* A Vector containing the BReelMovieElement
*/
private Vector _list;
/**
* The persistent store object
*/
private PersistentObject _persist;
/**
* Reads the list from persistence or creates
* a new one if not found
*/
public BReelMovieList() {
_persist = PersistentStore.getPersistentObject
(B_REEL_MOVIE_LIST);
Object obj = _persist.getContents();
if( obj == null) {
obj = new Vector();
//store the vector
_persist.setContents( obj );
_persist.commit();
}
_list = (Vector)obj;
_comparator = new BReelMovieElementComparator();
}
/**
* Saves the data to the store
*/
private void commit()
{
_persist.commit();
}
Let's cover what just happened. To start we have the usual package and import statements, along with the class declaration. The B_REEL_MOVIE_LIST variable is a constant that will be used as a unique ID for the persistent database. The _list variable will contain all of the BReelMovieElements, and _persist is the persistent store object. The constructor has been marked as private because we don't want to be able to instantiate the object from outside of the class. The constructor is also responsible for retrieving the data from the store. The last piece included in the code above is a commit() method which will be responsible for actually storing the vector to persistence.
/////////////////////////////////////////////////
// Implementation of Readable List //
/////////////////////////////////////////////////
public Object getAt(int index)
{
return _list.elementAt(index);
}
public int getAt(int index, int count, Object[] elements,
int destIndex)
{
return 0; //not implemented
}
public int getIndex(Object element)
{
return _list.indexOf(element);
}
public int size()
{
return _list.size();
}
/////////////////////////////////////////////////////////
// Implementation of WriteableList Interface //
/////////////////////////////////////////////////////////
public void add( Object element )
{
//check if the element is grouped, if not, do it now
if( !ObjectGroup.isInGroup( element )) {
ObjectGroup.createGroup( element );
}
//we are going to add an element in a sorted order
int size = size();
for (int cnt = 0; cnt < size; ++cnt) {
BReelMovieElement compare =
(BReelMovieElement)_list.elementAt(cnt);
if (_comparator.compare(element, compare) < 0) {
insertAt(cnt, element);
return;
}
}
//the element wasn't added, so it goes to the bottom of the list
_list.addElement(element);
commit();
}
public void insertAt( int index, Object element )
{
//check if the element is grouped, if not, do it now
if( !ObjectGroup.isInGroup( element )) {
ObjectGroup.createGroup( element );
}
_list.insertElementAt(element, index);
commit();
}
public void remove( Object element )
{
_list.removeElement(element);
commit();
}
public void removeAll()
{
_list.removeAllElements();
commit();
}
public void removeAt( int index )
{
_list.removeElementAt(index);
commit();
}
These methods are just an implementation of the List interface for all of the writeable accessors. We start off with the readable methods, and then move to the writeable methods. With all of the writable methods you need to make sure that you commit the data to persistence. The add method is also doing a bit of extra work. We don't want to just add a new movie element into the mix of things, so we'll add it in a sorted way based on the movie title.
/////////////////////////////////////////////////////////
// Implementation of ListFieldCallback Interface //
/////////////////////////////////////////////////////////
public void drawListRow(ListField listField, Graphics graphics,
int index, int y, int width) {
//put a check in as a precaution so that we don't
//throw an out of bounds exception
int size = size();
if (size > 0 && index < size) {
BReelMovieElement element = (BReelMovieElement)getAt(index);
graphics.drawText(element.getMovieTitle(), 0, y, 0, width);
}
}
/**
* Used to assit with the search
* @param listField
* @param index
* @return Movie Title
*/
public Object get(ListField listField, int index) {
BReelMovieElement element = (BReelMovieElement)getAt(index);
return element.getMovieTitle();
}
public int getPreferredWidth(ListField listField) {
return Graphics.getScreenWidth();
}
/**
* Performs a search of data in the field
* @param listField
* @param prefix
* @param start
* @return
*/
public int indexOfList(ListField listField, String prefix,
int start) {
return listField.indexOfList(prefix,start);
}
public void insertItem(int index, Object element) {
}
}
The last part of our list class is implementing the ListFieldCallback. We want to make the list responsible for the drawing of the elements, which is where the drawListRow() method comes into play. When an element is drawn we are choosing to just draw the movie title. Other methods worth noting are the indexOffList(), and get() methods. These two methods combined will allow simple searching of the list field, adding some extra power to the application and making it easy for the user to find what they want.
The comparator is pretty simple. The main concern is the compare() method which compares the movie titles from the sorted list.
package com.bbdj.samples.breel;
import net.rim.device.api.util.Comparator;
class BReelMovieElementComparator implements Comparator {
public BReelMovieElementComparator() {
// Do nothing.
}
public int compare( Object o1, Object o2 ) {
// compare the movie titles
BReelMovieElement element1 = (BReelMovieElement)o1;
BReelMovieElement element2 = (BReelMovieElement)o2;
return (StringUtilities.compareToIgnoreCase
(element1.getMovieTitle(),
element2.getMovieTitle()) );
}
public boolean equals( Object obj ) {
// We don't need to know this
return false;
}
}
We have successfully built the foundation for the application, and are ready to move onto the next phase. Let's take a break and move into the user interface scene. The first thing we want to create is a method that allows us to keep a single tracking point for the list of our movies. Also down the road this class will become useful for different features, but that's for the second chapter of this story that will be published in the next issue.
package com.bbdj.samples.breel;
import net.rim.device.api.system.RuntimeStore;
final class BReel
{
/**
* ID used to store the object in the runtime store
*/
//com.bbdj.samples.breel.BReel
private static final long APP_ID = 0x1db8890f0df27f91L;
/**
* List of all the Movie Elements
*/
private BReelMovieList _list;
/**
* Constructor
*/
private BReel() {
_list = new BReelMovieList();
}
/**
* Retrives a single instance of BReel
* @return current instance of BReel
*/
/* package */ static BReel getInstance() {
RuntimeStore store = RuntimeStore.getRuntimeStore();
Object bReel = store.get(APP_ID);
if (bReel == null) {
bReel = new BReel();
store.put(APP_ID, bReel);
}
return (BReel)bReel;
}
/**
* @return The data from persistence
*/
/* package */ BReelMovieList getList() {
return _list;
}
}
Everything here is pretty basic, with the possible exception of the getInstance(). This is a static method that retrieves the single instance of BReel so that we only work with one instance of the BReelMovieList. As you can see, the constructor is private so only the getInstance() method can invoke it. Once we have created the instance we also provide a method from pulling out the list for the screens to use.
The next class is currently a very simple class, but it is the main driver class, or the entry point for the application. When the user clicks on the icon the main method is invoked and 3...2...1...action!
package com.bbdj.samples.breel;
import net.rim.device.api.ui.UiApplication;
final class BReelApp extends UiApplication
{
public static void main(String args[]) {
new BReelApp().enterEventDispatcher();
}
public BReelApp() {
pushScreen(new BReelScreen());
}
}
Finally! We actually get to see what something is going to look like. BReelScreen at the moment is a basic screen, but it's still better than nothing. The screen itself only contains a list field that will list all of the movies in our database. On top of that we have some menu items which take care of opening, delete, or creating a new movie entry.
package com.bbdj.samples.breel;
import net.rim.device.api.system.GlobalEventListener;
import net.rim.device.api.ui.*;
import net.rim.device.api.ui.component.*;
import net.rim.device.api.ui.container.MainScreen;
final class BReelScreen extends MainScreen
implements GlobalEventListener {
/**
* ID used to identify when a screen update has to take place
*/
//com.bbdj.samples.breel.BReelScreen
public static final long SCREEN_UPDATE = 0x7d2f34f5416d5bb5L;
//menu item varaiables
private MenuItem _deleteItem =
new MenuItem("Delete", 500001, 600000) {
public void run() {
deleteMovie();
}
};
private MenuItem _newItem =
new MenuItem("New", 500000, 600000) {
public void run() {
addNewMovie();
}
};
private MenuItem _openItem =
new MenuItem("Open", 500001, 500000) {
public void run() {
openMovie();
}
};
//UI Variables
private BReelMovieList _list;
private ListField _listField;
/* package */ BReelScreen() {
//set the title of the screen
setTitle("BReel Movie Database");
//get the move list
_list = BReel.getInstance().getList();
//create the list field and set the callback
_listField = new ListField();
_listField.setSearchable(true);
_listField.setCallback(_list);
_listField.setSize(_list.size());
//add the field to the screen
add(_listField);
//add the listener to the screen
UiApplication.getUiApplication().addGlobalEventListener(this);
addMenuItem(_newItem);
}
Let's take a step back from the production floor and head to the editing room to figure out how the shot went. We start with some basic declarations and imports. Note that the screen extends MainScreen, which means that our screen will have a default set of behaviours. Added to that we implement the GlobalEventListener, which will be used to update the ListField from classes that are outside of the screen but can affect the data.
A key is defined for our screen update event, and then we jump into defining the menu items for the screen and what exactly they are going to do. When a movie is highlighted we want the default menu action to be the open action, so we are giving it a lower priority value than the other two menu items.
Shifting into the constructor, the masterpiece begins. For an esthetic effect we are adding a title to the screen. Now create the list field, allow it to be searchable, set the callback and size and add it to the screen. The last step that the constructor must do is to register itself to be a global event listener so that it gets notified when an update is necessary.
//make sure we remove the listener
public void close() {
UiApplication.getUiApplication().
removeGlobalEventListener(this);
super.close();
}
public void makeMenu(Menu menu, int instance) {
if (_list.size() > 0) {
menu.add(_openItem);
menu.add(_deleteItem);
}
super.makeMenu(menu, instance);
}
In these methods we are just adding to the default behaviour of the screen. When close() is called and there is only one screen on the display stack, then the screen is popped from the stack and system.exit(0) is called. Before the application is ended we want to un-register the global event listener. Overriding the makeMenu() class allows us to control what menu items are displayed to the user. This is important because you wouldn't want an open, or delete, menu item when there is nothing in the list.
/**
* Gives user ability to add a new movie to the list
*/
/* package */ void addNewMovie() {
UiApplication.getUiApplication().pushScreen
(new BReelMovieElementScreen());
}
/**
* Deletes a movie eletment from the list
*/
/* package */ void deleteMovie() {
//get the selected item, and remove it
int index = _listField.getSelectedIndex();
_list.removeAt(index);
//set the size of the list file which causes invalidate
_listField.setSize(_list.size());
}
/* package */ void openMovie() {
BReelMovieElement element =
(BReelMovieElement)_list.getAt(_listField.getSelectedIndex());
UiApplication.getUiApplication().pushScreen(
new BReelMovieElementScreen(element, false));
}
Actions have now been defined for the menu items. One of the reasons that the actions have been placed in a separate method is because your could override the keychar() method on the listField or screen to capture user input. Most commonly, the enter key would be mapped to the open item, and the delete/backspace key mapped to the delete item. The add and open actions both use the same screen but just pass in different parameters for the required action.
/////////////////////////////////////////////////
// Implementation of GlobalEventListener //
/////////////////////////////////////////////////
public void eventOccurred(long guid, int data0, int data1,
Object object0, Object object1) {
if (guid == SCREEN_UPDATE) {
_listField.setSize(_list.size());
}
}
}
The last thing that we need to worry about in this screen is the implementation of the global event listener. In this case it's simple. Just check if the guid passed into the eventOccurred() method is the same as the SCREEN_UPDATE ID. If that's the case, then size the list field accordingly which causes the list field to invalidate itself.
The climax to this plot has been reached and we are heading to the conclusion, but don't worry. There's plenty of action left to be had! The BReelMovieElementScreen provides a way for the user to create and review detailed information about any movie in the list on the BReelScreen.
package com.bbdj.samples.breel;
import net.rim.device.api.system.ApplicationManager;
import net.rim.device.api.ui.*;
import net.rim.device.api.ui.component.*;
import net.rim.device.api.ui.container.*;
class BReelMovieElementScreen extends MainScreen
implements FieldChangeListener
{
//menu items
private MenuItem _deleteItem =
new MenuItem("Delete", 500000, 500000) {
public void run() {
doDelete();
}
};
private MenuItem _saveItem =
new MenuItem("Save", 500000, 500000) {
public void run() {
//only close the screen if the save was successful
if (onSave()) {
close();
}
}
};
//variables
private BReelMovieElement _element;
private boolean _updated;
private boolean _isNew;
//field variables
BasicEditField _titleField;
BasicEditField _prodCompanyField;
BasicEditField _lentToField;
ObjectChoiceField _genreChoiceField;
ObjectChoiceField _formatChoiceField;
ObjectChoiceField _lentChoiceField;
BReelMovieElementScreen() {
this(new BReelMovieElement(), true);
}
More of the basics start this scene, with a few small surprises. Like the previous screen we are again extending MainScreen, but this time we are implementing a FieldChangeListener which will add some excitement to the story shortly. Basic menu declarations and variables used in the class along with the screen's fields. The last part shows a constructor which seems to be leading to something bigger.
BReelMovieElementScreen(BReelMovieElement element,
boolean isNew) {
_element = element;
_isNew = isNew;
setTitle("B Reel Movies");
//make sure we have no null strings
String title = _element.getMovieTitle();
String prodCompany = _element.getProdCompany();
String lentTo = _element.getLentTo();
int genreChoice = _element.getGenre();
int formatChoice = _element.getFormat();
boolean lentChoice = _element.getLentOut();
//add the fields to the screen according to the element data
_titleField = new BasicEditField("Title: ", title,
BasicEditField.DEFAULT_MAXCHARS,
BasicEditField.NO_NEWLINE);
add(_titleField);
_prodCompanyField = new BasicEditField("Film Company: ",
prodCompany,
BasicEditField.DEFAULT_MAXCHARS,
BasicEditField.NO_NEWLINE);
add(_prodCompanyField);
String genreChoices[] =
{"Horror", "Drama", "Sci-Fi", "Comedy", "Action"};
_genreChoiceField =
new ObjectChoiceField("Genre: ", genreChoices, genreChoice);
add(_genreChoiceField);
String formatChoices[] = {"DVD", "VHS"};
_formatChoiceField =
new ObjectChoiceField("Format:", formatChoices, formatChoice);
add(_formatChoiceField);
String lentChoices[] = {"No", "Yes"};
_lentChoiceField =
new ObjectChoiceField("Lent Out:", lentChoices,
(!lentChoice ? 0 : 1));
_lentChoiceField.setChangeListener(this);
add(_lentChoiceField);
if (lentChoice) {
_lentToField = new BasicEditField("Lent To: ", lentTo,
BasicEditField.DEFAULT_MAXCHARS,
BasicEditField.NO_NEWLINE);
add(_lentToField);
}
//add the menu items
addMenuItem(_saveItem);
if (!_isNew) {
addMenuItem(_deleteItem);
}
}
While it might seem like there is a lot of code, everything that occurs in this constructor is pretty basic. The main purpose is to create the fields and add them to the screen with pre-populated information depending on the calling constructor. There are a few things to point out here. The _lentToField is only displayed if the movie is lent out, and the delete menu item is only added if we have opened a movie from the previous screen.
public void close() {
if (_updated) {
ApplicationManager.getApplicationManager().
postGlobalEvent(BReelScreen.SCREEN_UPDATE);
}
super.close();
}
/**
* Deletes the element from the list
*/
/* package */ void doDelete() {
int answer = Dialog.ask(Dialog.D_DELETE);
if (answer == Dialog.DELETE) {
BReelMovieList list = BReel.getInstance().getList();
list.remove(_element);
_updated = true;
close();
}
}
public boolean onSave() {
//get the data from the screen
BReelMovieElement newElement = new BReelMovieElement();
String title = _titleField.getText();
//if the title is blank then we can't save
if (title == null || title.length() == 0 ) {
Dialog.inform("Cannot have a blank title!");
return false;
}
newElement.setMovieTitle(title);
newElement.setProdCompany(_prodCompanyField.getText());
newElement.setGenre(_genreChoiceField.getSelectedIndex());
newElement.setFormat(_formatChoiceField.getSelectedIndex());
if (_lentChoiceField.getSelectedIndex() == 1) {
newElement.setLentOut(true);
}
if (newElement.getLentOut()) {
newElement.setlentTo(_lentToField.getText());
}
//remove the old element and add the new one
BReelMovieList list = BReel.getInstance().getList();
list.remove(_element);
list.add(newElement);
_updated = true;
return true;
}
The three methods above are a combination of overriding the screen’s default methods for additional functionality and adding our own method for the delete function. Closing this screen will fire an update to the BReelScreen, but only if the element has been saved, or deleted. The delete method displays a confirmation dialog so that the user doesn't accidentally delete the element. The onSave() is just responsible for pulling the information from the fields on the screen and dropping it into our BReelMovieList. It should be pointed out that the element can't be saved if the movie title is blank. That's just some artistic flavoring, and is not necessary for your application.
/////////////////////////////////////////////////
// Implementation of FieldChangeListener //
/////////////////////////////////////////////////
public void fieldChanged(Field field, int context) {
//the listener is only added the a choice field, so cast it
ObjectChoiceField choiceField = (ObjectChoiceField)field;
//if the choice is 0 (NO) clear the field
if (choiceField.getSelectedIndex() == 0) {
getDelegate().delete(_lentToField);
_lentToField = null;
//if the choice is 1 (YES), add the field
} else if (choiceField.getSelectedIndex() == 1) {
_lentToField = new BasicEditField("Lent To: ", "",
BasicEditField.DEFAULT_MAXCHARS,
BasicEditField.NO_NEWLINE);
add(_lentToField);
}
}
}
That's it! Oh wait, watch the credits, and hidden in the credits is our implementation of the FieldChangeListener. The lent out field is an ObjectChoiceField which lets the user toggle between YES or NO. When the field is changed to YES, the listener is notified of the change event, and the lentToField is added to the screen. When the field is changed to NO, the field is removed from the screen.
Yes. You have reached the end of the story and the application is complete, so get your thumbs ready to type your movie collection into your BlackBerry device. And as always in the movie business these days TO BE CONTINUED ..... Watch for B Reel II: Attack of the Feature Enhancements.
Please email your comments, suggestions and editorial submissions to