Rapid Cordova application development
5 Feb 2014
Developing Apache Cordova (aka Phonegap) applications tends to be rather painful: the long waits while compiling an application for Android and uploading it to the device (or emulator) leads to unacceptable round trip times, especially for applications with lots of large content files (images, videos). In this blog post we’ll explain how we develop Cordova applications directly in the desktop web browser, and announce an open source release of a RequireJS module that helps making this a little easier for us.
The main reason we’ve chosen Cordova over native development is portability: writing a native app for each platform you want to support is a waste of developer effort and, by extension, money. We would also like to leverage this portability to the fullest by developing applications from the browser, as much as we can. In a browser, pressing “refresh”, results in an instantaneous update.
Waiting for the (non-existent) device
Very basic applications can be developed in the browser without
problems, but this isn’t so easy once you start using some Cordova
plugins to make use of the device’s more advanced capabilities. When
you use these, you generally need to wait for the
deviceready
event to get triggered. This signals that Cordova has finished
setting up all plugins and handlers and you can start using these.
The initial “demo” application which gets installed when you
initialise a new Cordova project also uses the deviceready
event as
a sort of starting point for the application. This means that when
you open it in your web browser, it won’t seem to “do” anything,
because it’s waiting for an event which will never arrive. This is
what you’ll see when you open it in a browser:
Because the event is never triggered, it will stay stuck in this state rather than giving an error. On an actual device, this is what you’d see:
What we really want is for the application to detect that it’s not running on a device and continue the application with that knowledge. Furthermore, we are using the excellent RequireJS package as a module system. To integrate cleanly into the rest of our code base, we would like to specify that our code depends on device capabilities in a declarative fashion, much like the RequireJS DOM Ready plugin allows us to specify “the DOM being ready” as a “dependency” of our module. It turns out that this isn’t very difficult.
Detecting the Cordova environment
Phonegap/Cordova is set up in such a way that the initial HTML page
contains a <script>
tag which loads a non-existent phonegap.js
or
cordova.js
file. The reason for this is that each supported
platform (Android, iOS, BlackBerry, …) has its own
JavaScript-to-native “bridge” which allows JavaScript to invoke native
code. So the phonegap.js
file is installed in the platform-specific
directory when you add a new platform. Then, when you “build” your
application, your own files are installed alongside these
previously-installed files in the platform directory, and the whole
“environment” is bundled up and uploaded to the device, where the
<script>
tag works as expected.
Because this phonegap-script is not loaded on the desktop, none of the
variables it declares are available. We can make use of this to
detect the presence of Cordova, simply by checking whether
window.cordova
is defined. This allows to register the
deviceready
-handler only when we’re actually on the device, and
calling the same handler with a different value on the desktop. The
callback handler can then dispatch to load different code depending on
the situation.
We have wrapped up this logic into a nice RequireJS component, which we are releasing today as free software. It is dual licensed under the “new BSD” and MIT licenses. Check it out on GitHub!
Additionally, we’ve published it as a Bower component, to make integrating it into your code base even easier.
Case study: database support
As it turns out, the native “web view” components in iOS and Android (which Cordova uses internally) make absolutely no guarantees about the persistence and maximum available space in any of the “HTML5” storage options. For some versions of Android, the amount of data you can store in a Web SQL database and HTML Local Storage is as low as 5 MB. iOS is even worse: in newer versions it may “randomly” decide to drop your “persistent” data.
This presents us with a big limitation: we want our data to stay safe, and we can’t know in advance how much storage we’ll need. Failing at seemingly-arbitrary points in time is not an option! Luckily, there is a relatively simple solution: we can use Phonegap plugins to access native code which can make those guarantees. After some research, we decided that the Cordova SQLite plugin is our best option. It works on iOS, Android and there’s also a Windows mobile version, and it tries to offer an API which conforms to the W3C Web SQL Database standard.
Even though Web SQL is deprecated and will likely be removed from browsers in the future, as things stand today it’s our best option for developing in the browser and deploying to a device without making any changes to the application. Web SQL does not make any guarantees about storage space either, but we don’t care about that so much when developing in the browser.
The SQLite plugin doesn’t work as a “polyfill” but has its own
interface endpoint: window.sqlitePlugin
. Furthermore, it will only
be available from JavaScript after the deviceready
event has fired.
The code to make this difference “invisible” is very simple using our
RequireJS plugin. Our application’s sqldb
module looks like this:
define(['deviceReady!'], function(isCordova) {
'use strict';
var dbRootObject = isCordova ? window.sqlitePlugin : window;
if (typeof dbRootObject.openDatabase == 'undefined') {
window.alert('Your browser has no SQL support! Please try a Webkit-based browser');
return null;
}
var db = dbRootObject.openDatabase('my-database', '', 'My database', null),
// ...Some more convenience and specific schema creation code...
return db;
});
With this, the underlying database implementation is completely
transparent to the code that uses it, which can simply require
the
above module:
var sqldb = require('sqldb');
sqldb.transaction(function(t) {
t.executeSql('SELECT col1 FROM table WHERE col2 = ?', [value], ...);
});
Like I said, support for Web SQL is unfortunately being phased out, and Mozilla has already taken the step of removing Web SQL from Firefox. There’s a WebSQL addon which tries to restore Web SQL support, but it is too basic for our needs. Our application will run in production for years to come (hopefully!), so being able to perform schema migrations is pretty essential for us. Who cares about versions, indeed!
Anyway, with the code above we are able to run our application straight from the file system in Chromium. There’s only one more hurdle to overcome, and that’s this message:
XMLHttpRequest cannot load file:///home/peter/src/test/www/app/view/myview.html. Cross origin requests are only supported for HTTP
If we run the application from http://localhost
we won’t see this,
but because I really like to keep things as simple as possible I would
prefer not to use a web server if I don’t strictly have to. So, I
investigated why we get this message. It turns out that the
RequireJS text plugin uses
XMLHttpRequest to load resources, whereas regular RequireJS injects
<script>
tags into the HTML. The documentation for the text
plugin states:
Many browsers do not allow file:// access to just any file. You are better off serving the application from a local web server than using local file:// URLs. You will likely run into trouble otherwise.
Never one to take other people’s well-meant advice, I figured out that Firefox won’t complain like this (probably assuming that everything loaded over the file:// protocol is from the same “domain”), and in Chromium you can simply pass a flag when starting it:
$ chromium --allow-file-access-from-files
And that’s all there’s to it!