The FileConnection API BLACKBERRY

The JSR 75 FileConnection API gives your application the capability to read and write to the BlackBerry file system, both the internal flash memory and any memory card attached to your device. It also enables you to read data that other applications have written to the file system. This is especially useful for retrieving pictures, video, and other media that might be on your device. In the following sections, we’ll create a simple application to browse for photos from the device’s memory (internal or memory card) and display them on screen.

Basic Application Framework

You should be used to creating applications by now; create a new a new BlackBerry application called FileConnection. Start with an application class and main screen class. The initial classes are as follows:

package com.thinkingblackberry.fileconnection;
import net.rim.device.api.ui.UiApplication;
public class FileConnectionApplication extends UiApplication {
public FileConnectionApplication() {
FileConnectionScreen screen = new FileConnectionScreen();
pushScreen(screen);
}
public static void main(String[] args) {
FileConnectionApplication app = new FileConnectionApplication();
app.enterEventDispatcher();
}
}

FileConnectionScreen.java:

package com.thinkingblackberry.fileconnection;
import net.rim.device.api.ui.MenuItem;
import net.rim.device.api.ui.component.Menu;
import net.rim.device.api.ui.component.ObjectListField;
import net.rim.device.api.ui.container.MainScreen;
public class FileConnectionScreen extends MainScreen {
private ObjectListField fileList;
private String currentPath = "file:///";
public FileConnectionScreen() {
setTitle("FileConnection");
fileList = new ObjectListField();
fileList.set(new String[] {"store/", "SDCard/"});
add(fileList);
}
protected void makeMenu(Menu menu, int instance) {
super.makeMenu(menu, instance);
menu.add(new MenuItem("Select", 10, 10) {
public void run() {
loadFile();
}
});
}
private void loadFile() {
}
}

We’re introducing another new UI component here: the ObjectListField. This displays a vertical list of strings on screen. We’ll use it to show the contents of the directories as we browse. We’ve populated our object list field with two initial entries: store/ and SDCard/. These are the root directories for the internal device memory and the memory card, respectively, and are the same on every BlackBerry device. We use them as a starting point for browsing.

There’s also a String that contains the current path. When opening a FileConnection, you need the full path. we’ll use this variable to keep track of it. Finally, we’ve added a single menu item in the makeMenu method using an anonymous inner class as discussed in Chapter 4. We’ve also created a loadFile method that will contain all the FileConnection logic. For now, the application looks like Figure, with the default two items in the ObjectListField and the single custom menu item.

The FileConnection application main screen containing the two default directory entries

FileConnection application main screen containing the two default directory entries

Opening a File Connection

Classes related to the FileConnection API are found in the javax.microedition.io.file package.For this application,we’ll mostly work with the FileConnection interface. FileConnection is a pretty rich interface. It enables you to create and delete files, list the contents of a directory, and read and write file contents and attributes. You obtain a FileConnection using the javax.microedition.io.Connector class.

The Connector class is also used to initiate network connections among other things, so we’ll be using it again in the future. All of its methods take a string parameter, which is a URL representing a resource. Connector URLs conform to the standard URL definition from RFC 2396, with a scheme portion (such as http:) that represents the type of resource being requested. The BlackBerry Javadocs thoroughly explain the details of the many different connection types; we’ll explore some of them when we discuss networking. For now, just be concerned with opening file resources.

File connection URLs start with “file://.”

For example,to open the store directory representing the device’s internal memory, you’d use the following code. Note the extra “/” at the beginning of the URL:

try {
FileConnection storeDirectory =
(FileConnection)Connector.open("file:///store/");
} catch (IOException e) {
}

As long as the URL represents a path that could be valid, no exception is thrown. This enables you to create a file by first opening a connection to a URL representing the file you want to create, and then calling FileConnection.create. For BlackBerry, a URL is valid if all directories specified in the URL exist, with the exception of the last one only if the path returns a file. For example, if the home directory exists under the store directory, and is empty (containing no subdirectories or files) then the following URL is allowed:

file://store/home/testfile.txt

and so is the following:

file://store/home/newdir/

but the following URL will cause an exception to be thrown, because newdir doesn’t exist:

file://store/home/newdir/test.txt

Listing the Directory Contents

he first thing we’ll implement is listing files and subdirectories in a directory.Whenever you click the Select menu item, if the currently highlighted item in the object list field is a directory, we’ll replace the items in the list field with the contents of that directory. When the user clicks Select, we’ll construct a path to that directory by simply concatenating the current path with the path of that directory; all directory entries end with a “/”character so we don’t have to worry about adding that.

The FileConnection.isDirectory method tells you if the file connection points to a directory.If it does, the list method retrieves an Enumeration of Strings, which are the pathnames of the files and directories contained within the directory. Because ObjectListField requires an Object array, we’ll add the strings from the enumeration one by one to a Vector, and get the array from that Vector when we’re done. Here’s the code for loadFile:

private void loadFile() {
currentPath += fileList.get(fileList, fileList.getSelectedIndex());
try {
FileConnection fileConnection = (FileConnection)Connector.open(currentPath);
if (fileConnection.isDirectory()) {
Enumeration directoryEnumerator = fileConnection.list();
Vector contentVector = new Vector();
while(directoryEnumerator.hasMoreElements()) {
contentVector.addElement(directoryEnumerator.nextElement());
}
String[] directoryContents = new String[contentVector.size()];
contentVector.copyInto(directoryContents);
fileList.set(directoryContents);
}
}catch (IOException ex){
}
}

Run the application, and you should be able to navigate through the device’s file system by selecting directories and clicking the Select menu item.

Highlight SDCard,and then open the menu and click Select.

Highlight SDCard,and then open the menu and click Select.

Highlight BlackBerry, and then open the menu and click Select.

Highlight BlackBerry, and then open the menu and click Select.

Browsing through the SD card's file structure

Browsing through the SD card's file structure

Viewing Pictures

Now we’ll add the code to loadFile to actually view pictures. We will create a new screen containing a single BitmapField to view our picture; whenever we highlight a picture in the file list and click Select, we’ll create a new instance of this screen and push it onto the stack.

The Image Display Screen

Make a new file called ImageDisplayScreen.java in your project. The code for Image Display Screen is simple:

package com.thinkingblackberry.fileconnection;
import net.rim.device.api.system.EncodedImage;
import net.rim.device.api.ui.component.BitmapField;
import net.rim.device.api.ui.container.MainScreen;
public class ImageDisplayScreen extends MainScreen {
public ImageDisplayScreen(EncodedImage image) {
BitmapField bitmapField = new BitmapField();
bitmapField.setImage(image);
add(bitmapField);
}
}

This uses an EncodedImage instead of a Bitmap because that’s the way we’ll load the image from the file system. EncodedImage has a few extra features over Bitmap including scaling, support for multiple frames, and support for more file types.

Loading Images from the File System

For any FileConnection, you can retrieve an InputStream. This enables us to read bytes from the file. The only checking that we’re doing is that the path ends in a known file extension. For purposes of this exercise, you don’t need to worry about error handling, but if this were a production application, we’d add more. The following code should be added to loadFile, right after the if statement:

else if (currentPath.endsWith(".jpg") || currentPath.endsWith(".png")){
InputStream inputStream = fileConnection.openInputStream();
InputStream inputStream = fileConnection.openInputStream();
byte[] imageBytes = new byte[(int)fileConnection.fileSize()];
inputStream.read(imageBytes);
inputStream.close();
EncodedImage eimg = EncodedImage.createEncodedImage(imageBytes, 0,
imageBytes.length);
UiApplication.getUiApplication().pushScreen(new ImageDisplayScreen(eimg));
}

Getting Images into the Simulator

Before you run the application, you need a few pictures on the simulator’s file system to use for testing. You can get these by running the camera application on your simulator. If you have a webcam attached to or built in to your PC, you might get to snap an actual picture. Otherwise, you are presented with a dialog box to choose an image to represent the picture the camera takes. In either case, take a picture or two and start the FileConnection application again. Unless you explicitly saved your picture to a different location, navigate to SDCard/BlackBerry/pictures, and you should see the image listed.

A list of images in the pictures directory

A list of images in the pictures directory

If you click on one of those images, you should see something like Figure.

The image?

The image?

You might get a clearer image if you selected a lower resolution picture from the dialog box, but if you took a higher-resolution picture using the camera on a real device, what you see is the extreme upper left corner of that photo. The BlackBerry’s LCD screen is generally a lot lower resolution than its camera, so to display the full image, you need to scale it. This is another reason we used EncodedImage instead of Bitmap; it has better built-in image-scaling support.

Scaling the Image

Though not directly relevant to persistent storage, scaling the image will make the application complete, and it is useful to know how to do it We’ll do the image scaling within ImageDisplayScreen’s constructor. We’ll present the code first and discuss it after. The new constructor for ImageDisplayScreen is:

public ImageDisplayScreen(EncodedImage image) {
int displayWidth = Fixed32.toFP(Display.getWidth());
int imageWidth = Fixed32.toFP(image.getWidth());
int scalingFactor = Fixed32.div(imageWidth, displayWidth);
EncodedImage scaledImage = image.scaleImage32(scalingFactor, scalingFactor);
BitmapField bitmapField = new BitmapField();
bitmapField.setImage(scaledImage);
add(bitmapField);
}

NOTE: EncodedImage uses 32-bit fixed point decimal numbers as its scale factors. The BlackBerry provides support for numbers in this format in the net.rim.device. api.math.Fixed32 class.Fixed32 enables you to store a decimal number in a 32-bit int; 16 bits are used for the integer portion and 16 bits are used for the decimal portion. Addition and subtraction operations on Fixed32 numbers are faster than on floats or doubles, so they can be a good choice for those types of decimal arithmetic. When using Fixed32 numbers, remember to convert back and forth from regular ints; that’s what the toFP method does. You also must use Fixed32 methods for multiplying and dividing Fixed32 format numbers. Using the* and / operators produces nonsensical results, but you can use the standard Java + and – operators for addition and subtraction.

For EncodedImage, a scaling factor of between 0 and 1 means scale up; a scaling factor of greater than 1 means scale down. We can get the correct scaling factor for our image by dividing the image’s width by the BlackBerry display’s width. To be completely precise, we could check the image and display height, too, but I’ll leave that as an exercise for you. When you run the FileConnection application and select the same image now, you’ll get a much better picture.

The correctly scaled image

The correctly scaled image

This is as far as we’ll go with reading files from the file system. There are, of course, areas where the application can be improved, but at this point, you should have a good understanding of how to read files and directories using the FileConnection API. You can also try to run this application on a real device, and you should be able to view photos that were taken by the device’s camera. Now let’s explore the other half of the FileConnection API: creating and writing to files on the file system.

Writing to the File System

We’ll extend the FileConnection application to enable writing to the file system. We can leverage the same directory browsing code and add functionality that enables you to make a copy of an existing image in the same directory. The functionality will be as follows: when a file (not a directory) is highlighted, a Copy menu item is available. When you click this menu item, you are prompted for a name for the new file, and if that file name doesn’t exist, the selected file is copied into a new file with the specified name.

A Dynamic Menu Item

First, we’ll add the new menu item to let us copy the currently selected file. Before we add the menu item, let’s put in place the method that we’ll call to do the work of copying the file. Add this to FileConnectionScreen:

private void copyFile(){
}

Now we want the Copy menu item to show up only when a file is selected. We know which entries in the object list field are files because the directories all end with the “/”character. Because we’re constructing the menu in the makeMenu method, which is called every time the menu key is clicked, we can easily check at that time if the currently selected item ends in “/”", and add the Copy menu item only if it does not. Add the following code to the FileConnectionScreen’s makeMenu method:

String selectedItem = (String)fileList.get(fileList,
fileList.getSelectedIndex());
if (!selectedItem.endsWith("/")) {
menu.add(new MenuItem("Copy", 10, 10) {
public void run() {
copyFile();
}
});
}

If you run the application now and browse around, you’ll see that the Copy menu item shows up only when a file is highlighted.

The Copy menu item shows up only when a file is selected.

The Copy menu item shows up only when a file is selected.

The File Name Screen

When we copy a file, we’ll pop up a dialog asking for a name for the new copy. This requires us to create a new screen with an edit field for the name. We’ll subclass net.rim.device.api.container.PopupScreen to get a dialog rather than a full screen. Note that the constructor for PopupScreen asks for the delegate manager, just as the constructor for Screen does.We’ll just use a VerticalFieldManager. We’ll also add a ButtonField so there’s some way to dismiss the screen (remember to set the ButtonField. CONSUME _CLICK style).

Finally, we’ll provide a method in the screen class to retrieve the name of the file from the edit field. The FileNameScreen class looks like this:

package com.thinkingblackberry.fileconnection;
import net.rim.device.api.ui.Field;
import net.rim.device.api.ui.FieldChangeListener;
import net.rim.device.api.ui.component.ButtonField;
import net.rim.device.api.ui.component.EditField;
import net.rim.device.api.ui.container.PopupScreen;
import net.rim.device.api.ui.container.VerticalFieldManager;
public class FileNameScreen extends PopupScreen implements FieldChangeListener {
private EditField fileNameField;
private ButtonField okButton;
public FileNameScreen() {
super(new VerticalFieldManager());
fileNameField = new EditField("New Filename:", "");
add(fileNameField);
okButton = new ButtonField("OK", ButtonField.CONSUME_CLICK |
Field.FIELD_HCENTER);
okButton.setChangeListener(this);
add(okButton);
}
public String getFilename() {
return fileNameField.getText();
}
public void fieldChanged(Field field, int context) {
if (field == okButton) {
close();
}
}
}

Copying the File

To display the file name screen, we’ll use UiApplication.pushModalScreen instead of pushScreen. This just means that the method won’t return until the file name screen is closed, this is the functionality we want in this case because we can’t copy the file until you’ve entered a file name and clicked OK:

FileNameScreen screen = new FileNameScreen();
UiApplication.getUiApplication().pushModalScreen(screen);
String newFilename = screen.getFilename();

After getting the file name, we’ll open a connection to the full URL for that filename and use FileConnection.exists to check to see if there’s already a file there. If so, we’ll display a dialog and exit the method:

FileConnection newFileConnection =
(FileConnection)Connector.open(currentPath + newFilename);
if (newFileConnection.exists()){
Dialog.alert("The file '" + newFilename + "' already exists!");
newFileConnection.close();
return;
}

If the file doesn’t exist, call FileConnection.create to create it. Then, open an OutputStream to the new file:

newFileConnection.create();
OutputStream newFileOutputStream =
newFileConnection.openOutputStream();

From that point on, the code is the same as when loading an image, except that instead of constructing an image with the byte array, we’re writing it to the OutputStream that represents the newly created file. Here’s copyFile in its entirety:

private void copyFile() {
// Prompt for the new filename
FileNameScreen screen = new FileNameScreen();
UiApplication.getUiApplication().pushModalScreen(screen);
String newFilename = screen.getFilename();
try {
FileConnection newFileConnection =
(FileConnection)Connector.open(currentPath + newFilename);
if (newFileConnection.exists()) {
Dialog.alert("The file '" + newFilename + "' already exists!");
newFileConnection.close();
return;
}
// The file doesn't exist, so we'll create it
newFileConnection.create();
OutputStream newFileOutputStream = newFileConnection.openOutputStream(); / Open the old file
currentPath += fileList.get(fileList, fileList.getSelectedIndex());
FileConnection fileConnection = (FileConnection)Connector.open(currentPath);
InputStream inputStream = fileConnection.openInputStream();
// Copy the contents of the old file into the new one
byte[] fileContents = new byte[(int)fileConnection.fileSize()];
inputStream.read(fileContents);
newFileOutputStream.write(fileContents, 0, fileContents.length);
inputStream.close();
newFileOutputStream.close();
Dialog.inform("Successfully copied the file!");
}catch (IOException ex) {
}
}

Now run the application to try it out. Browse to the SDCard/BlackBerry/pictures folder and copy one of the files there.

Entering the new file name; be sure to include the correct extension.

Entering the new file name; be sure to include the correct extension.

because our application doesn’t dynamically reload the directory, you have to exit and browse back to the pictures directory to see your new file.

The new file

The new file

You can select and view this file just as you could the original.



Face Book Twitter Google Plus Instagram Youtube Linkedin Myspace Pinterest Soundcloud Wikipedia

All rights reserved © 2018 Wisdom IT Services India Pvt. Ltd DMCA.com Protection Status

BLACKBERRY Topics