spacer

PhoneGap's File API

Posted in Mobile, JavaScript | Posted on 03-09-2012 | 1,202 views

This week I had the opportunity to record a few videos on PhoneGap for Adobe TV. One of the videos covered the File API and since it was a bit difficult I figured I'd share my findings, and sample code, with others.

My main struggle with the File API was trying to wrap my head around how it worked. The docs weren't entirely clear to me and were a bit confusing. Turns out there's a good reason for that. (Although I'm working to improve the docs.) PhoneGap's File API is really an implementation of the W3 File API. The PhoneGap docs mention something similar in the database area so it makes sense for the File docs to be updated as well. (And as I said - I'm working on that. I did my first pull request to add just such a mention.)

After I figured that out, I then found an incredibly useful article on the File API over at HTML5 Rocks: Exploring the Filesystem APIs. I encourage everyone to read over Eric Bidelman's article. He's got examples for pretty much every part of the API.

At a high level, working with the File API comes down to a few basic concepts:

  • First, you request a file system. You can ask for either a persistent or temporary file system. On the desktop, these both point to a sandboxed folder. On PhoneGap, your access is a bit broader, essentially the entire storage system.
  • The API supports basic "CRUD" operations for both files and folders.
  • The API supports reading and writing to files, both binary and plain text.
  • Probably the most difficult aspect (well, not difficult, just a bit unwieldy), is that each and every operation is asynchronous. So to get and read a file involves about 3 or 4 levels of callbacks.

For my Adobe TV video, I built a simple application that demonstrates some of these principles. I began with a few simple buttons that would let me test basic file operations:

spacer

In order to do anything, I need access to the file system, and this needs to be done after PhoneGap fires the deviceready event:

view plain copy to clipboard print about
1function onDeviceReady() {
2
3    //request the persistent file system
4    window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, onFSSuccess, onError);
5    
6}
7
8function init() {
9    document.addEventListener("deviceready", onDeviceReady, true);
10}

If the file system is loaded, onFSSuccess will handle storing a pointer to it while also setting up my event handlers:

view plain copy to clipboard print about
1function onFSSuccess(fs) {
2    fileSystem = fs;
3
4    getById("#dirListingButton").addEventListener("touchstart",doDirectoryListing);            
5    getById("#addFileButton").addEventListener("touchstart",doAppendFile);            
6    getById("#readFileButton").addEventListener("touchstart",doReadFile);            
7    getById("#metadataFileButton").addEventListener("touchstart",doMetadataFile);            
8    getById("#deleteFileButton").addEventListener("touchstart",doDeleteFile);            
9    
10    logit( "Got the file system: "+fileSystem.name +"<br/>" +
11                                    "root entry name is "+fileSystem.root.name + "<p/>")    
12
13    doDirectoryListing();
14}

As a quick aside, getById is simply a wrapper for document.getElementById. (Trying to reduce my dependency on jQuery.) Our fileSystem object has a few properties we can display, like the name for example. It also has a root property which is a pointer to the root directory. (Duh.) The logit function is simply appending to a DIV on the HTML page as a quick debugging technique.

spacer

This event handler then fires off doDirectoryListing. This is normally run by the "Show Directory Contents" button but I automatically run it after the file system is opened.

view plain copy to clipboard print about
1function gotFiles(entries) {
2    var s = "";
3    for(var i=0,len=entries.length; i<len; i++) {
4        //entry objects include: isFile, isDirectory, name, fullPath
5        s+= entries[i].fullPath;
6        if (entries[i].isFile) {
7            s += " [F]";
8        }
9        else {
10            s += " [D]";
11        }
12        s += "<br/>";
13        
14    }
15    s+="<p/>";
16    logit(s);
17}
18
19function doDirectoryListing(e) {
20    //get a directory reader from our FS
21    var dirReader = fileSystem.root.createReader();
22
23    dirReader.readEntries(gotFiles,onError);        
24}

Reading bottom to top, the event handler starts off by creating a reader object off the root property of the file system object. To get the files, you simple call readEntries, and use a callback to handle the result. The entries (which can be files or directories) are a simple array of objects. Here's an example of the output:

spacer

So what about file reading and writing? Opening a file is simple. You can simply run getFile(name) and the API can (if you want) also create the file if it doesn't exist. This simplifies things a bit. Here's the event handler and call back for clicking "Creating/Append to Test File".

view plain copy to clipboard print about
1function appendFile(f) {
2
3    f.createWriter(function(writerOb) {
4        writerOb.onwrite=function() {
5            logit("Done writing to file.<p/>");
6        }
7        //go to the end of the file...
8        writerOb.seek(writerOb.length);
9        writerOb.write("Test at "+new Date().toString() + "\n");
10    })
11
12}
13
14function doAppendFile(e) {
15    fileSystem.root.getFile("test.txt", {create:true}, appendFile, onError);
16}

Again - please read up from bottom to top. You can see the use of getFile here along with the options after it to ensure an error won't be thrown if it doesn't exist. Appending to a file is done by creating a writer object. Do note - and I screwed this up myself - if you don't seek to the end of the file you'll actually overwrite data as opposed to appending. Now let's look at reading:

gipoco.com is neither affiliated with the authors of this page nor responsible for its contents. This is a safe-cache copy of the original web site.