Phonegap - Real persistent storage module (incl jQuery deferred example)

There are several possibilities to store persistent data for a mobile app in Phonegap. But because you cannot be not sure that native HTML capabilities, e.g. localStorage is really stored permanently (see discussion on GoogleGroups), I decided to share my little RequireJS module prototype. It uses JSON and the mobile filesystem which enables also the possibility to use the data even after the app was completely deleted and reinstalled.

Requirements:

define(["jquery", "utils", "cordova"], function($, utils, cordova) {

  "use strict";

  /**
   * Filename to store data in
   */
  var fileName = "myproject.db"

  /**
   * Store cache object
   * (the app reads from here to avoid unnecessary file accesses)
   */
  var store = {};

  /**
   * Is the module initialized, see isReady() method.
   */
  var status = $.Deferred();

  /**
   * Common error handler
   * @param error
   */
  function fail(error) {
    status.resolve();
    utils.log(error, 'Error in Storage!');
  }

  /**
   * Get file entry for the fileName 
   * (if not existing the file is created)
   * @param callback where the fileEntry is used
   */
  function getFileEntry(callback) {
    var options = {create : true,
                   exclusive : false};
    window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, 
      function(fileSystem) {
        fileSystem.root.getFile(fileName, options, function(fe) {
          callback(fe);
        });
    }, fail);
  }

  return {

    /**
     * Check if the storage is ready to use.
     */
    isReady : function() {
      return status;
    },

    /**
     * Initiates the storage module for usage
     * (for local development in a browser, 
     * HTML5's localStorage is used)
     */
    init : function() {

      if (!utils.isBrowserMode()) {
        
        getFileEntry(function(fileEntry) {

          fileEntry.file(function(file) {

            var reader = new FileReader();
            reader.onload = function(evt) {

              if (evt && evt.target && evt.target.result) {

                // Just a little debug output 
                // to see the file content
                utils.log(evt.target.result, 'Settings');

                // readed JSON is set on local cache obj
                store = $.evalJSON(evt.target.result);
              } else {
                utils.log(evt, 'read error ' + fileEntry.fullPath, 
                          'Error loading from device.');
              }
              // if finished then resolve the deferred command
              status.resolve();
            };

            // Error handling
            reader.onerror = function(err) {
              utils.log(err, 'Storage reading error '
                   + fileEntry.fullPath);
              status.resolve();
            };
            // read the file as text
            reader.readAsText(file);
          });
        });

      } else {
        utils.log({}, "Browser mode, using localStorage!");
        store = localStorage;
        status.resolve();
      }
    },

    /**
     * Set value
     * 
     * @param name
     * @param value
     */
    set : function(name, value) {

      // Set in cache
      store[name] = value;
      var encoded = $.toJSON(store);

      // Write to file
      if (!utils.isBrowserMode()) {

        getFileEntry(function(fileEntry) {

          fileEntry.createWriter(function(writer) {
            writer.truncate(0);
            writer.onwriteend = function(evt) {
              // console.log("write success " + encoded);
            };
            writer.write(encoded);

          }, fail);
        });
      }
    },

    /**
     * Get value
     * 
     * @param name
     */
    get : function(name) {

      var value = null;
      if (store[name]) {
        value = store[name];
      }
      return value;
    }
  };
});

Currently this is based on a deferred pattern, because in this case the storage has to be fully loaded before the authentication is initialized. If there is no need for synchronous waiting, also a custom event can be triggered. Just remove the deferred and isReady() and replace resolve() with triggering an event. If using jQuery:

$("body").trigger({
  type:"storageready"
});

No comments:

Post a Comment