Skip to main content

Getting Started with Java and SQLite on Blackberry OS 5.0

March 17, 2010

{cs.r.title}







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 Same

If 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:

  • As an embedded database library, you need to create your own database instance before you can start creating tables.
  • You’ll need to execute INSERT, SELECT, UPDATE, and DELETE to handle the respective CRUD operations within the db instance.
  • If you want to group some commands together in an atomic operation, then be sure to leverage the database’s capability for transactions so that you can roll back changes if a failure occurs midstream in the process.

So, with all the formalities out of the way, let’s look at the code needed to create your first SQLite database.

Prerequisites

In order to run the example code provided in this article, you will need to download the Blackberry JDE 5.0 SDK.

Creating Your First Database

Of 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 Card

In 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 SQLite

Programmatically 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 Application

One 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){

                        }
                    }
                }
            }
        }
    }

Conclusion

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.

Acknowledgements

Thanks 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.


width="1" height="1" border="0" alt=" " />
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
Related Topics >> Databases   |   GUI   |   J2ME   |   Mobility   |   Featured Article   |