Article

Overcoming Phonegap Memory Limits With Local Storage

During development of Turkey Volume Guessing App, it began to crash at random on the iPad 2. Being a Phonegap app, it was the age old problem of memory limits in Mobile Safari. In this case the app is generating three large uncompressed png images which must be stored for use at different points in the workflow. Mobile Safari has strict memory limitations and usually resolves issues by shutting the browser down.

Here, the issue is more than inefficiency. The app flow depends on these three large images in order to allow back-stepping through the process. Fortunately, HTML5 client-side storage came to the rescue.

Of the three storage mediums available, only LocalStorage and WebSQL are reliably available in PhoneGap. LocalStorage is the easiest to implement but it has a 5 megabyte limit. WebSQL has more programming overhead, but gives you a more liberal 50 megabytes. I already had done a WebSQL based cache for another project so I decided to go with that.

Creating a Cache in WebSQL

The WebSQL cache is a simple 3 field table:


var cachedb = openDatabase('localcache', '1.0', 'Mobile Client DB', 2 * 1024 * 1024);
cachedb.transaction(function (tx) {
    tx.executeSql("CREATE TABLE IF NOT EXISTS tblCache (cacheId INTEGER PRIMARY KEY, cacheName TEXT, cacheData TEXT, cacheTimeStamp INTEGER)");
});

With a Getter and a Setter:


function Get(pKey) {
    return new Promise(function(resolve, reject) {
        cachedb.transaction(function (tx) {
            tx.executeSql('SELECT * FROM tblCache WHERE cacheName=?', [pKey], function(tx, result){
                try{
                    resolve(result.rows.item(0).cacheData);
                }catch(err){
                    reject(err);
                }
            }, function(result, error){reject(error);});
        });
    });
}
function Set(pKey, pValue) {
    return new Promise(function(resolve, reject) {
        // Check for an existing key
        cachedb.transaction(function (tx) {
            tx.executeSql('SELECT * FROM tblCache WHERE cacheName=?', [pKey], function(tx, result){
                if( result.rows.length > 0 ){
                    tx.executeSql('UPDATE tblCache SET cacheData=?, cacheTimeStamp=? WHERE cacheId=?',
                        [pValue, Date.now(), result.rows.item(0).cacheId], function(tx, response){
                            status = result.rows.item(0).cacheId;
                            console.log('Updating tblCache: '+status);
                            resolve(status);
                    }, function(result, error){console.log(error);reject(error);});
                }else{
                    tx.executeSql('INSERT INTO tblCache (cacheName, cacheData, cacheTimeStamp) VALUES ( ?, ?, ? )',
                        [pKey, pValue, Date.now()], function(tx, response){
                            status = response.insertId;
                            console.log('Inserting into tblCache: '+status);
                            resolve(status);
                    }, function(result, error){console.log(error);reject(error);});
                }
            }, function(result, error){console.log(error);reject(error);});
        });
    });
}

Then you can add data to the cache by passing it with an arbitrary key:


Set('canvasPhoto', base64Data).then(function(result) {
    console.log('data cached');
}).catch(function(error) {
    console.log('something went wrong');
});

Pass the key to the Get function to retrieve the data.

For Turkey Volume Guessing App I use the toDataUrl() method of the <canvas> element. It returns the current canvas state as a base64 encoded string which can be saved to the cache. The cached images can be pulled directly into the canvas when needed in the app flow. Of course, the images still take up memory when displayed. But in a case like this one where you’re simply passing along images from step to step, client side storage is the way to go.

The cache can be used to store any string data. I’ve used this technique to cache html content within other apps. It stores the Listmas ad used within Turkey Volume Guessing App. Stored data is long-term and will survive app upgrades. Though my example uses WebSQL, the basic technique applies to all forms of client-side storage in HTML5.