Phonegap Application Development
How many times have you heard, "there's an app for that"? But sometimes, there actually isn't "an app for that", or the apps that do exist don't meet your needs. As Linux users, we tend to like to scratch our own itches, and if that means we write some code to do it, so be it. However, writing code to run on an Android phone or tablet has a bit of a learning curve, and it's even worse on Apple products. Fortunately, Phonegap provides a simple way to create standalone apps for Android, iPhone, WebOS, Blackberry and Windows Phone, among others. You just need to be reasonably proficient in HTML, JavaScript and CSS, and you can develop native apps for the majority of smartphones currently in use. And, the same code base can run, with obvious limitations, on any Web browser.
Developing native code for Android is relatively easy. You'll have to learn to use Android's XML-based screen layout mechanism, and you'll have to learn Java. For iPhone, you'll need to learn Objective C. If you want to develop for Windows Phone, you'll need to learn C# as well. Instead, you simply could use Phonegap and maintain a singe code base in HTML/JavaScript/CSS. This is the definition of a "no-brainer".
Before I go much further, I need to clear up a potential source of confusion. Phonegap initially was developed by a company named Nitobi, which subsequently was acquired by Adobe. In 2011, Nitobi/Adobe donated the Phonegap code base to the Apache Foundation. As a result of this contribution, they needed to ensure that the intellectual property was unfettered by trademark ambiguity, so they renamed the Phonegap project to Cordova. The Apache Foundation is in the process of migrating from Phonegap to Cordova, so I refer to this project as Cordova here.
Getting started with Cordova on Android isn't difficult. At the risk of rehashing material that is well documented elsewhere, I'll just outline the process involved. First, you have to install the Android SDK, which is a free download from the Android site and is very well documented. The Android SDK integrates with the Eclipse IDE, so you will need to have a fairly recent version of Eclipse as well. The SDK documentation will walk you through the whole process, from downloading the software to building and running the sample application. The SDK lets you run your program in an emulator or on a real Android device, if you have one.
Installing Cordova is also fairly straightforward and well documented. The only difficulty I had with the entire process is that I wasn't very familiar with Eclipse and stumbled a bit. The Cordova installation process culminates with building and running the sample Cordova application. The sample application demonstrates much of Cordova's API and is worth looking at.
I found the process of creating a new Cordova project to be a bit kludgey. The process involved creating a new Android project first, then making a two-line modification to a Java program, pasting in a dozen lines of XML into another file, and well, you get the idea. All of the changes made sense, but seemed a bit error-prone. Finally, I decided to copy the example project and strip it down to its bare necessities. This is the approach that I recommend; it worked like a champ for me.
A Cordova application has three main pieces. There is an architecture-specific binary piece that actually communicates directly with the device's hardware. Then there is a Java-based abstraction layer that sets up your application's runtime environment and presents a JavaScript API for your application to use. The third part is your HTML/JavaScript/CSS code, and this is the only part that you normally need to be concerned with. All of these pieces get linked together at build time to form a native binary executable for the target device.
The Cordova JavaScript API allows your program to access many of the host device's sensors. This means that your application has easy access to the device's GPS, accelerometer, compass, microphone and speaker. The API provides persistent data storage by allowing access to the device's contact database, its filesystem and a native SQLite database.
Let's look at some code.
For the sake of illustration, I developed a simple application. The application is designed to demonstrate three main features: access to the device's GPS sensor, access to the user's contacts and the ability to make Ajax calls to remote Web services.
The HTML needed to create this application is pretty straightforward. See Listing 1.
<html>
<head>
<h3>Sample Application</h3>
<meta name="viewport" content="width=320; user-scalable=no" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<link rel="stylesheet" href="./master.css" type="text/css"
↪media="screen" title="no title">
<script type="text/javascript" charset="utf-8"
↪src="cordova-1.9.0.js"></script>
<script type="text/javascript" charset="utf-8"
↪src="main.js"></script>
</head>
<body style="{background: beige;}">
<img id="picture" height="100px" width="200px" border="0"
↪src="http://www.linuxjournal.com/files/linuxjournal.com/
↪ufiles/logo-lj.jpg">
<br>
<p>
You are here:<br>
<input name="lon", id="lon" size="15"> Longitude, <br>
<input name="lat", id="lat" size="15"> Latitude.<br>
<p>
<input id="id" name="id"><br>
<input id="name" name="name"><br>
<input id="phone" name="phone"><br>
<input id="email" name="email"><br>
<button onclick="previous_contact();">Previous</button>
<button onclick="next_contact();">Next</button>
<p>
<hr>
"<span id="quote">Linux Rocks!</span>"<br>
<span id="error"></span><br>
<hr>
</body>
</html>
The content of the <head> section is mostly boilerplate. Note that you import the cordova.js and then your main.js JavaScript files, and that the order is important. In the <body>, you find a graphic that you bring in from a remote server. Then you see input fields for your current GPS coordinates. Next, you have some form fields that will contain information from the phone's contact directory, followed by Previous and Next buttons that allow users to scroll through their contacts. Finally, there are two <span>s that will allow the program to display witty comments from a remote Web site and any error messages that might need to be displayed. Figure 1 shows what the page looks like in a browser.
Figure 1. Sample Application Running in a Browser
Listing 2 shows the JavaScript code that makes it all work.
1 var mobile = 1;
2 var contacts;
3 var current_contact = 0;
4
5 function init () {
6
7 if (mobile == 1) {
8 navigator.contacts.find(["*"], store_contacts);
9 }
10
11 update();
12 window.setInterval(update, 1000);
13 }
14
15 function update () {
16 var req;
17
18 if (mobile == 1) {
19 navigator.geolocation.getCurrentPosition
↪(set_location,location_error);
20 } else {
21 document.getElementById("lat").value =
↪Math.floor(Math.random()*46)
22 document.getElementById("lon").value =
↪Math.floor(Math.random()*46)
23 }
24
25 req = new XMLHttpRequest();
26
27 if (!req) {
28 alert("Ajax failed!");
29 return false;
30 }
31
32 req.open("GET", "http://example.com/test.html", true);
33 req.onreadystatechange = set_quote;
34 req.send(null);
35
36 return true;
37 }
38
39 function store_contacts (c) {
40 contacts = c;
41 display_contact();
42 return true;
43 }
44
45 function previous_contact () {
46 current_contact = current_contact - 1;
47 if (current_contact < 0) { current_contact = 0; }
48 display_contact();
49 return true;
50 }
51
52 function next_contact () {
53 current_contact = current_contact + 1;
54 if (current_contact > (contacts.length-1)) { current_contact =
↪contacts.length-1; }
55 display_contact();
56 return true;
57 }
58
59 function display_contact () {
60 document.getElementById("id").value = " ";
61 document.getElementById("name").value = " ";
62 document.getElementById("phone").value = " ";
63 document.getElementById("email").value = " ";
64
65 document.getElementById("id").value =
↪contacts[current_contact].id;
66 document.getElementById("name").value =
↪contacts[current_contact].displayName;
67 document.getElementById("phone").value =
↪contacts[current_contact].phoneNumbers[0].value;
68 document.getElementById("email").value =
↪contacts[current_contact].emails[0].value;
69
70 return true;
71 }
72
73 function set_location (p) {
74 document.getElementById("lat").value = p.coords.latitude;
75 document.getElementById("lon").value = p.coords.longitude;
76 return true;
77 }
78
79 function location_error (e) {
80 document.getElementById("error").innerHTML = e.message;
81 return true;
82 }
83
84 function set_quote (p) {
85 if (!p) { return 1; }
86 if ((p.status) && (p.status > 299)) { return 1; }
87 document.getElementById("quote").innerHTML = this.responseText;
88 return true;
89 }
90
91 if (mobile == 1) {
92 document.addEventListener("deviceready", init, false);
93 } else {
94 window.onload = init;
95 }
Line 1 is a simple boolean flag that determines whether the script is running on a mobile device or a Web browser. Setting this variable to 0 allows me to run and debug the program in Firefox where I have all of the HTML, DOM and JavaScript development tools that I'm accustomed to using. Setting this variable to 1 targets the program for a mobile device where I can debug the Cordova-specific aspects of my program, knowing that my JavaScript is probably correct.
Lines 91–95 arrange to have the JavaScript init()
function called
when the DOM is loaded and after the Cordova initialization routines
have run. These lines also point out a couple oddities about Cordova
development. First, there is no way to detect automatically whether
the program is running in a browser or on a smartphone. That's why I
set that variable, as discussed earlier. Also, Cordova creates its own
event that gets triggered when it's ready to begin JavaScript execution;
you can't use window.onload
as you normally would, because this event might
trigger before Cordova is ready. Either way, the init()
function will
be called at the appropriate time.
The init()
function is on lines 5–13. On line 8, you make a call to the
contacts.find
method to get an array of contact objects from the device's
contact directory. This array is then passed, asynchronously,
to store_contacts()
, lines 39–44, which simply stores the array in a global
variable. Then, init()
makes a call to
update()
to initialize the data
display and arranges for update()
to be called every second from then on.
The update()
function, lines 15–37, is where the fun begins. If the
program is running in a browser, you simply populate the Longitude
and Latitude fields with random numbers. Having the numbers
change like that allowed me to verify that the program was still
running. However, if the program is running on a physical device,
you use the geolocation.getCurrentPosition
method to fetch the real
GPS coordinates. If this operation is successful,
set_location()
is
called. Otherwise, location_error()
gets called, and you can display an
error message (lines 73–83). The only error I've encountered with the
getCurrentPosition
call was when I actually had the GPS disabled.
Lines 25–36 form an almost embarrassing Ajax call. I've stripped this code down to the least amount of code that would run under Firefox and Cordova. It won't run on IE, and it doesn't do much, if any, error checking. I'm not trying to demonstrate how to do an Ajax call in Cordova. I'm only trying to demonstrate that you can. In this case, you're loading some content from a remote server and putting it inside the quote <div> discussed earlier. During development, I'd simply change the content of that file on the server to verify that it changed inside the app.
Lines 44–58 are onclick handlers for the two buttons in the
application. All these routines do is adjust an array index plus or
minus one, as appropriate, and do some bounds checking. Finally, they
call display_contact()
to display the current contact.
The display_contact() function (lines 59–72) is the last of the Cordova-specific functions in the program. In lines 60–64, you blank out all of the contact fields in preparation for setting them with new values. I found that if I didn't blank them out first, they would persist into the next record if the next record didn't happen to have a value for a given field. In lines 65–69, you populate the fields with data from the current contact record. Note that both phoneNumbers and e-mails are arrays of objects, and that for this purpose, you are interested only in the first element.
And there you have it. There's nothing here that would be unfamiliar to the average Web developer, except a really powerful API. But, I've only touched on what this API can do. Figure 2 shows the application running on my Droid Bionic.
Figure 2. Sample Application Running on Android
I did some hand waving over a subtle problem with this application that many JavaScript, particularly Ajax, developers have encountered. On most browsers, your program can't load from one domain, and then load content or code from another domain. However, this program is standalone and needed to load content from a potentially arbitrary Web site. Cordova handles this problem by "whitelisting" the domains from which an application is allowed to fetch content. By default, all domains are blacklisted, and, thus, all network access is disabled. A developer can whitelist a domain by editing /res/xml/cordova.xml and following the examples given for the <access> tag. This is a safe but elegant solution to a potentially nasty problem.
Another interesting possibility is to have your application load all of its HTML and JavaScript from a remote Web server. This easily can be done by making a simple change to ./src/{projectname}/{projectname}.java. This file has only 20 lines of real code, and the necessary change is pretty intuitive.
Being able to load content from a remote server actually makes development easier. I found it easier to do my development on a remote, publicly accessible server, than to develop on my workstation. This way, I could point my Web browser at the application and get all my HTML, CSS and JavaScript working the way I wanted. Then I got the application fully functional on my Android. Once it was fully functional, I copied the project to my workstation for the final build. Doing it this way is the only way you'll be able to test an application that makes any Ajax calls without violating your browser's cross-site scripting security policy.
As neat as Cordova is, there are a few things I didn't like. As I mentioned earlier, there is no way to detect automatically whether a program is running in a browser or on a device. Also, I found the whitelisting functionality to be a bit buggy, but not in any way that broke my application. But, the most disheartening thing I found was when I tried to use the camera API. Instead of simply snapping a picture and either returning the data or storing it, the API actually brought up the device's native camera device as a pop-up. This was extremely intrusive and actually broke my first demonstration app.
I've had a lot of fun playing with Cordova, and I've barely scratched the surface of what it can do or be extended to do. This has to be the easiest way to get into smartphone application development.