The Blackberry OS is one of the most widely deployed Java ME implementations worldwide, with millions of business users that run mobile devices that use Java within the operating system. Released in 2009, the Blackberry OS 5.0 includes several new features that are of interest to today?s mobile Java developers, such as JSR-135 video recording and 3G graphics support with JSR-239 OpenGL ES API. Most notably for enterprise developers, the Blackberry OS 5.0 is the first mobile operating system that will include database support for Java ME developers who wish to store relational data.
The purpose of this article is to show developers how to get started in creating applications that utilize the SQLite database engine for Blackberry OS 5.0 applications. SQLite is an extremely popular database library that is used for wireless, embedded, and seamless-install application scenarios where a full scale database is not feasible or suitable. Since SQLite is an embedded database library, you (the developer) will have less visibility into the database itself. You won?t be able to use a db viewing tool to maintain the database from another computer, since there?s no way to connect you to SQLite db over a network. This article provides you with a simple tool, DBUtil.java, that will allow you to verify the existence of your db, and monitor its size.
No JDBC, but the Concepts are Still the SameIf you?re wondering if the Blackberry SQLite Java API is JDBC compliant, let me clarify that it?s not. The JSR-169 API (JDBC API for Java ME) has been standardized for years, but unfortunately, there are no commercially available mobile phones that support database development with it. Please note, however, that although the Blackberry SQLite Java API doesn?t support the JSR-169 API, the basic concepts relating to programmatic access to the database are still the same:
So, with all the formalities out of the way, let?s look at the code needed to create your first SQLite database.
PrerequisitesIn order to run the example code provided in this article, you will need to download the Blackberry JDE 5.0 SDK.
Creating Your First DatabaseOf course, since SQLite is an embedded database engine that?s optimized for mobile environments, the overhead to create a database is extremely simple. Listing 1, located below, shows the two lines of code necessary to physically create an instance of the SQLite database on Blackberry OS 5.0 devices.
Listing 1. The Code Required to Create a SQLite Database
URI uri = URI.create("file:///SDCard/Databases/database1.db");
sqliteDB = DatabaseFactory.create(uri);
As you can see, it?s extremely trivial to create a db instance with SQLite! Please note that although you may have the capability to create your database instance in various locations on the filesystem, it?s recommended that you create your database on external media, such as SD cards (especially if you intend to store large amounts of data). Listing 2, located below, is the source code for FirstSQLiteApp.java, which is a complete working example of a Java application that creates a database.
Listing 2. Full Source Code for FirstSQLiteApp.java.
import net.rim.device.api.ui.*;
import net.rim.device.api.ui.component.*;
import net.rim.device.api.ui.container.*;
import net.rim.device.api.database.*;
import net.rim.device.api.io.*;
public class FirstSQLiteApp extends UiApplication {
public FirstSQLiteApp() {
pushScreen(new InnerClassScreen());
}
public static void main(String[] args){
FirstSQLiteApp firstSQLiteApp = new FirstSQLiteApp();
firstSQLiteApp.enterEventDispatcher();
}
class InnerClassScreen extends MainScreen {
public Database sqliteDB;
public InnerClassScreen() {
LabelField title = new LabelField("Create DB Application",
LabelField.ELLIPSIS | LabelField.USE_ALL_WIDTH);
setTitle(title);
add(new RichTextField("Initializing db create process..."));
try{
URI uri = URI.create(
"file:///SDCard/Databases/database1.db");
sqliteDB = DatabaseFactory.create(uri);
add(new RichTextField(
"Status: Database was successfully created."));
} catch (Exception e){
System.out.println(e.getMessage());
add(new RichTextField(
"Status: Database was not created."));
add(new RichTextField(e.getMessage()));
e.printStackTrace();
}
}
}
}
Now, after we run the application within the Blackberry Simulator, we?ll get the following result as shown in Figure 1 below.
Figure 1. The Results of Executing FirstSQLiteApp.java
Please note that by default, the Blackberry OS 5.0 simulator doesn?t have a simulated SD loaded within the environment, so you?ll have to load one in order for the code in Listing 2 to work properly.
Loading a Simulated SD CardIn order to load a simulated smart card into the Blackberry OS 5.0 simulator, you need to access the ?Simulate > Change SD Card?? menu item while the simulator is running. Additionally, you need to be prepared to load a DMP file in order to finish configuration of the simulated smart card. You can find several sample DMP files in the Research In Motion\BlackBerry JDE 5.0.0\simulator folder. If the simulator provides an error message stating that the card needs to be formatted, then proceed with the formatting procedure.
Performing CRUD Operations with SQLiteProgrammatically retrieving, storing and updating data within the SQLite database is also a very trivial task. The code in Listings 3-6 demonstrates how to create tables, insert, and select data from the database.
Listing 2. Creating Tables in the Database
try {
URI uri = URI.create("file:///SDCard/Databases/database1.db");
sqliteDB = DatabaseFactory.open(myURI);
Statement st = sqliteDB.createStatement( "CREATE TABLE 'Employee' ( " +
"'Name' TEXT, " +
"'Age' INTEGER )" );
st.prepare();
st.execute();
}
catch ( Exception e ) {
System.out.println( e.getMessage() );
e.printStackTrace();
}
Listing 3. Inserting Data into the Database
try {
URI uri = URI.create("file:///SDCard/Databases/database1.db");
sqliteDB = DatabaseFactory.open(myURI);
Statement st = sqliteDB.createStatement(
"INSERT INTO Employee(Name,Age) " +
"VALUES ('Ralph',47)");
st.prepare();
st.execute();
}
catch ( Exception e ) {
System.out.println( e.getMessage() );
e.printStackTrace();
}
Listing 4. Selecting Data from the Database try { URI uri = URI.create("file:///SDCard/Databases/database1.db"); sqliteDB = DatabaseFactory.open(myURI); Statement st = sqliteDB.createStatement("SELECT * FROM Employee"); st.prepare(); Cursor c = st.getCursor(); Row r; while(c.next()) { r = c.getRow(); String name = r.getString(0) ; Integer age = r.getInteger(1); } } catch ( Exception e ) { System.out.println( e.getMessage() ); e.printStackTrace(); }
Creating and Using the DBUtility ApplicationOne of the downsides of working with embedded databases is the lack of visibility. A traditional database runs on its own process on the CPU, binds to its own port on the network adapter, and can easily be remotely managed and configured. Conversely, embedded databases such as SQLite run in the process of your application, have no connection to the network, and must be managed and configured by applications that are resident on the host machine. Therefore, I created a simple JSR-75 (FileConnection API) application that allows developers to have more visibility into their embedded databases. When you run the DBUtility.java MIDlet, you can navigate the filesystem of your device to debug database creation issues, as well as to easily determine the file size of your embedded database. Figure 2, located below, shows how the DBUtility can browse your mobile device?s filesystem, and Figure 3 shows the file size of an individual file. Listings 5 and 6 contain all the source code necessary to build the DBUtility MIDlet.
Figure 2. Browsing the Filesystem Using DBUtility.java
Figure 3. Viewing the File Size of a File Using DBUtility.java
Listing 5. DButility.java
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import java.io.*;
import java.util.*;
import javax.microedition.io.*;
import javax.microedition.io.file.*;
import javax.microedition.lcdui.*;
public class DBUtility extends MIDlet implements CommandListener {
private Command exitCommand; // The exit command
private Display display; // The display for this MIDlet
private FileNavigator fileNavigator = null;
public DBUtility() {
display = Display.getDisplay(this);
exitCommand = new Command("Exit", Command.EXIT, 0);
fileNavigator = new FileNavigator(this);
}
public void startApp() {
Runnable r = new Runnable() {
public void run() {
display.setCurrent(fileNavigator.getListofFolder(
"",true)); //fileViewer.
}
};
new Thread(r).start();
}
public void pauseApp() {
}
public void destroyApp(boolean unconditional) {
}
public void commandAction(Command c, Displayable s) {
if (c == exitCommand) {
destroyApp(false);
notifyDestroyed();
}
}
}
Listing 6. FileNavigator.java
import javax.microedition.midlet.*;
import java.io.*;
import java.util.*;
import javax.microedition.io.*;
import javax.microedition.io.file.*;
import javax.microedition.lcdui.*;
/**
* This class traverses a filesystem and displays the folders
*and files within the filesystem in a List. This class creates the
* List, directoryList, to show its contents
*
* This class may also be renamed fileUtilty
*/
class FileNavigator implements CommandListener{
private Command backCommand1;
private Command selectCommand;
private Image folder_closed;
private Image folder_open;
private Image plain_file;
private MIDlet midlet;
//
// this is the main connection to the filesystem
// we'll use this object over and over
//
private FileConnection fileConn = null;
// These are vectors for the file/directory names,
// images, and URLs for the directory/files
// the boolean is used to determine if the item is
// a folder or file
private Vector fileNames = null;
private Vector images = null;
private Vector curr_dir_urls = null; // the urls for
// the files/folders in the current directory
private Vector fileTypes = null; // used to determine
//whether the element is a file or directory
// I'm assuming the fact that I'll need a class level variable
// that has the current "level" that we're at
// the folder with all the system roots is level '0'
int level = 0;
// I'm also assuming that I'm going to need a class level Vector
// where I'll store all my folder level urls
Vector level_urls = new Vector();
public FileNavigator(MIDlet _midlet){
midlet = _midlet;
selectCommand = new Command("Select", "Select", Command.OK, 1);
backCommand1 = new Command("Back", Command.BACK, 1);
try {
folder_closed = Image.createImage("/folder_closed.png");
plain_file = Image.createImage("/plain_file.png");
folder_open = Image.createImage("/folder_open.png");
} catch (Exception e) {
}
}
// this method returns a javax.lcd.List
// representing the files
// located in the folder found at the url
// passed in (if useRoots is false)
// if the useRoots variable is true, then the
//method will ignore the url string
// and return a List of the system roots
public List getListofFolder(String folder_url, boolean useRoots){
List directoryList = null; // this is the list that we'll return
Enumeration folder_contents = null; // the enumeration
//will contain all the contents of the current dir
// create the vectors, which will eventually be turned
// into arrays
fileNames = new Vector();
images = new Vector();
curr_dir_urls = new Vector();
fileTypes = new Vector();
// if useRoots is set to true, then obtain an enumeration of all
// the system roots (System roots may need to be explained)
if (useRoots == true){
folder_contents = FileSystemRegistry.listRoots();
} else {
// if we're in the else clause, then we need to use the
// passed in url to obtain a enumeration of the current dir
try{
fileConn = (FileConnection) Connector.open(folder_url);
// be sure to explain how to get hidden files
folder_contents = fileConn.list();
} catch (Exception e){
System.out.println(e);
new Alert(e.toString());
}
}
// we now need to provide a way to navigate
// out up of the folder (ie. cd ..)
// of course, we should provide this functionality for every
// folder EXCEPT the root
if (useRoots == true){
// set the level to "0"
level = 0;
} else {
fileNames.addElement("..");
images.addElement(folder_open);
curr_dir_urls.addElement(""); // this is an empty
// string null since that var isn't
// used for navigation
fileTypes.addElement(new Boolean(true));
// store the connection url in the level vector
// this will be used when we have to navigate ".."
level_urls.insertElementAt(folder_url, level);
// increment the level
level++;
}
// this is the rest of the old traverse method
while (folder_contents.hasMoreElements()) {
// 8-5, this looks like the items that will go in the vectors
// if so, then some additional comments may be needed
String item_name = (String)folder_contents.nextElement();
String item_url = null;
Image icon = null;
boolean isDirectory = false;
// since the enumeration only contains the filename,
// and not
// the full URL to the file, then we need set the full url
// in order to obtain the full information about
// each item in the folder
if (useRoots == true){
item_url = "file:///" + item_name;
} else {
item_url = fileConn.getURL() + item_name;
}
// here's the logic to set the proper image
// in the 'icon' variable
try {
FileConnection conn = (FileConnection)
Connector.open(item_url);
if(conn.isDirectory()){
icon = folder_closed;
isDirectory = true;
} else {
icon = plain_file;
isDirectory = false;
}
} catch (Exception e) {
System.out.println(e);
new Alert(e.toString());
}
// add the items to the vectors
fileNames.addElement(item_name);
images.addElement(icon);
curr_dir_urls.addElement(item_url);
fileTypes.addElement(new Boolean(isDirectory));
}
// initialize the arrays
String fileNameArray[] = new String[fileNames.size()];
Image imageArray[] = new Image[images.size()];
String urlArray[] = new String[curr_dir_urls.size()]; // this
// may not be needed since the vectors are global
Boolean fileTypeArray[] = new Boolean[fileTypes.size()]; // same
// for this one
// convert to arrays
fileNames.copyInto(fileNameArray);
images.copyInto(imageArray);
curr_dir_urls.copyInto(urlArray);
fileTypes.copyInto(fileTypeArray);
//create the List
directoryList = new List("Choose a folder or select a file",
Choice.IMPLICIT, fileNameArray, imageArray);
// add the navigation buttons
//here's the back button
directoryList.addCommand(backCommand1);
//here's the select button
directoryList.addCommand(selectCommand);
// add the inner class as the commandListener
directoryList.setCommandListener(this);
return directoryList;
}
public void commandAction(Command command, Displayable displayable) {
if(command == backCommand1){
// go to a previous screen
//Display.getDisplay(midlet).setCurrent(get_homeForm());
// test to see of the user hit the
// "select" button or the command action button
} else {
// get the selected index, which will be used shortly
int selectedIndex = ((List)displayable).getSelectedIndex();
if (((List)displayable).getString(selectedIndex).equals("..")){
// the user has selected the ".." directory
// so let's run some special logic to handle this
if(level > 1){
// this means that we should use the level_urls vector
// 8-6 perhaps we need to put the level-- right here
level = level - 2;
Display.getDisplay(midlet).setCurrent(
getListofFolder((String)
level_urls.elementAt(level), false));
} else{
// this means that we're at a low directory,
// so we need to show the roots
Display.getDisplay(midlet).setCurrent(
getListofFolder("", true));
level = 0;
}
// now decrement the level
//level--;
} else {
// otherwise, let's use the selected
// index to get the proper
// item in the List to navigate in the selected folder
// so, we've reached this area of code because
// we've come to
// the point where we will go further down a directory
// or select a file to see the size
// so obviously, we need to know if the user
// selected a file
// or a directory
boolean isFolderSelected = (
(Boolean)fileTypes.elementAt(selectedIndex)).
booleanValue();
if(isFolderSelected == true){
// the user selected a folder, so navigate down it
String folderUrl =
(String)curr_dir_urls.elementAt(selectedIndex);
Display.getDisplay(midlet).setCurrent(
getListofFolder(folderUrl, false));
} else {
// the user selected a file, so don't navigate
// start the process to discover Bluetooth devices
//**Display.getDisplay(midlet).
// setCurrent(get_fileSelectedAlert(), displayable);
// the user has obviously selected a file,
// so let's read it in
// this probably should be done in a new thread'
String file_url = (String)
curr_dir_urls.elementAt(selectedIndex);
//here
try{
fileConn = (FileConnection)
Connector.open(file_url);
long fileSize = fileConn.fileSize();
String stringFileSize = fileSize / 1024 + " kB";
Alert fileSelectedAlert = new Alert("",
"File Size: " + stringFileSize, null,
AlertType.CONFIRMATION);
fileSelectedAlert.setTimeout(Alert.FOREVER);
Display.getDisplay(midlet).
setCurrent(fileSelectedAlert);
System.out.println("File Size: " + stringFileSize);
} catch (Exception e){
}
}
}
}
}
}
For mobile Java developers, the Blackberry OS 5.0 platform provides a compelling development environment to enable developers to create exciting and full-featured applications. One of the most compelling features is the ability to create and manage a local relational database for persistent storage for your mobile application. This feature enables Java developers to bridge the gap between enterprise and mobile application development by incorporating a traditional paradigm for persistent data storage.
AcknowledgementsThanks to the Blackberry OS 5.0 development team for the extensive documentation and example code on the Blackberry OS 5.0 and SQLite Java APIs.
Bruce Hopkins author of Bluetooth for Java, is an enthusiast for mobile, embedded, and wireless application development. He's currently working for the startup BlogRadio. Bruce is also a Java Champion. AttachmentSize Figure1_sml.png66.61 KB Figure2_sml.png65 KB Figure3_sml.png59.13 KB
Najnowsze komentarze
3 weeks 5 days ago
5 weeks 1 dzień ago
5 weeks 3 days ago
7 weeks 55 min ago
7 weeks 3 days ago
7 weeks 4 days ago
7 weeks 5 days ago
8 weeks 3 hours ago
8 weeks 4 days ago
9 weeks 4 days ago