phonegap

If you know Javascript, I believe Phonegap offers the quickest way to develop a mobile app for several platforms (iPhone, Android, etc) in one go. You write HTML5, CSS3 and Javascript that calls a special API to access the device's GPS, acceleromoter, camera etc. It can be so easy - yet you can create very complex apps. It is underwritten by Adobe, so can be relied upon.

In November 2012 had to learn all about Phonegap very quickly to meet a deadline, and I fear my knowledge is not very deep. However, I present below the things I have learned because such information seems absent from the web. Every time I had a problem, and Googled, official resources were sparse, or did not cover my issue. I relied on StackOverflow heavily, and recommend it heartily.

Some of the install instructions here are for Windows PCs, though the code snippets are universal.

This information may not be perfect if you're reading this after 2017 as technology moves on...

Phonegap Build, not just Phonegap

To get a mobile app created as quickly as possible, I took shortcuts (good and bad). Since my background is Windows and ASP/SQL/Javascript not Java/C the first good shortcut was to use Phonegap Build to compile my app, instead of installing the eclipse IDE, the Java JDK, the Android SDK... Build is free for one app (you just keep overwriting the same code). I used my favourite text editor to write the .HTML and .JS files and then added a .XML configuration file. The Build website will accept a .ZIP file containing all these, and then generates the files that a device can download to install the app (e.g. .APK file for Android). It takes a minute to run, sometimes more at peak times.

The simplest possible build

1. Create a file called index.html and either stuff your JS and CSS in there, or put them in seperate files, linking to them just like any other web page. The HTML will look like any other webpage, but you must add this line:

<script src="phonegap.js"></script>

2. If you get a copy of phonegap.js you can load your app up in Chrome to check it works. Chrome can run the database API, though of course not much will happen if you ask for accelerometer data.

3. Create a config.xml file that contains the basic details about your app, such as this:

<?xml version="1.0" encoding="UTF-8"?>

<widget xmlns = "http://www.w3.org/ns/widgets"

xmlns:gap = "http://phonegap.com/ns/1.0"

id = "com.mags.wonderapp"

version = "1.0.0">

<name>Wondeful Appy</name>

<description>My lovely app</description>

<author href="http://mags.mags" email="mags@mags.mags">Magnus Smith</author>

<content src="index.html" />

<preference name="phonegap-version" value="3.6.3" />

<preference name="android-targetSdkVersion" value="11" />

<icon src="icon.png" />

<gap:config-file platform="ios" parent="CFBundleShortVersionString"><string>100</string></gap:config-file>

</widget>

4. Zip up your html, js and css files along with the xml config, but do not include a phonegap.js file, even if you have one. That would kill the build process. It is best to have copies of all the relevant jquery files, so you can refer to these with local paths, not the usual method of pointing to http://code.jquery.com/blah/etc as you do with a normal web page. Include your own icon.png (128x128) as it will make you feel good to see your own creation on screen!

5. Log into build.phonegap.com and upload your zip file. I kept all my zip files and numbered them so I could always go back to a successful app when I made a change and broke everything.

6. After a minute blue buttons will appear next to each of the operating systems for which compilation has taken place. The simplest way to see your new app in action is to take any mobile device that has a barcode reader app, and hold it up to the QR code on the screen. Beep! You usually get the file downloaded, and then are prompted to install it, subject to security restrictions about 'insecure' apps (i.e. apps which don't come from the official store).

Installing a Phonegap Build app on the Nexus 7

    1. Swipe down from the top right corner and select 'Settings'.
    2. Select 'Security' from near the bottom, and tick the box for 'Unknown sources'.
    3. Use the Google Play app store to download the 'Barcode Scanner' app.
    4. Use this to scan the QR code shown on your Build page.
    5. When the device beeps, select 'Open browser' which will appear to open a blank web page briefly.
    6. Swipe down from the top of the screen to see a notification about the installed program - click on it.
    7. Follow the instructions and conclude by using 'Open' to run the app.

I don't like emulators

Emulators enable you to see how various different devices might display your app, but they are difficult to install (for anyone who has used nothing but Windows programs), slow to load up and slow to run. With a real device in my hand I can go from a completed Build to using the app within 20 seconds. The emulator in eclipse takes me around 4 minutes. You'll have to test your app on a variety of screen sizes and operating systems at some point though! I gave up installing the Windows Phone emulator as it required an odd version of Visual Studio, plus a seperate download of something else...oh, I forget...it was too much hassle though.

Android emulator within eclipse IDE

To install:

1. download and install the bundle of eclipse/SDK/ADT and goodness knows what via phonegap website

2. fire up eclipse use the menu Window > Android Virtual Device Manager to manage the different gadgets you want to pretend to own

3. click New, give it any old name, pick a gadget off the list, and leave everything else to default

Each time you recompile your app:

1. the compiled apps are at build.phonegap.com/apps so download the .apk file they offer when you click the android icon

2. in eclipse use the menu Window > Android Virtual Device Manager, then click on a gadget, then click the Start button on the right

3. patiently wait for the black screen to turn into an image of a smartphone (dont rush ahead whilst the word "android" is shimmering)

4. open a command prompt (Start > Run > cmd) and navigate to the folder where you saved the SDK, and then into the platform-tools folder

5. type adb devices and it should tell you it has one device running, which is the one you started in step 3 (if it says "offline" then wait)

6. type adb install E:\myapps\Blah-debug.apk (or whatever the path to your .apk file is)

7. wait patiently for it to say "success" and then you can look at the emulator window (if you get "INSTALL_FAILED_ALREADY_EXISTS" then press F2 and pick "manage apps", click on your app and then press "uninstall")

8. if the gadget has locked, use F2 to unlock (the HOME key goes home, F2 is for menu, ESC is for back, see developer.android.com/tools/help/emulator.html for more)

9. single click the central icon of six-squares-in-a-circle at the bottom of the emulated screen, swipe left with the mouse, and there's your app

Note if you select "wipe" when starting an emulator from the AVD, the device behaves as if it is brand new (keeps popping up helpful hints) which means it removes the old copy of the app, and it's data.

Blackberry simulator

To install:

1. i downloaded a smartphone simulator from us.blackberry.com/sites/developers/resources/simulators.html (i picked the 9380 at random)

2. this seemed to require no setup, just launch from START > All Programs > Research in Motion and then find the program called 9380

Each time you recompile your app:

1. the compiled apps are at build.phonegap.com/apps so i downloaded the .jad file they offered when i clicked the blackberry icon

2. look at this file in notepad and it will list the names of all the .cod files you need (maybe 40 to 50 of them)

3. i noticed the .jad file was located at http://s3.amazonaws.com/blackberry.phonegap/slicehost-production/apps/12345/MyAppName.jad

so i changed the last bit of the URL to ".cod" (or "-1.cod" etc) to download the 40+ other files to the same folder (I did this once - next time I'll find a way to do them all in one go)

4. in the simulator, select File > Load and then multi-select all the .cod files

5. wait patiently, and then look in the main list of icons on the simulator's screen (click ALL to expand the menu, and then swipe upwards to scroll down)

6. i got an error "Error starting [app] module [name] not found" if i forgot to load a certain .cod file

Phonegap 'Gotchas'

Many times I was frustrated by the way my code was failing silently. A misspelt function name would not pop up a box and moan at you, it would just halt the code at that point without you knowing where it had died. Getting the app running on your PC first is possible using a browser that supports the relevant APIs, such as Chrome. You'd need to have a copy of phonegap.js at that point though.

Here's how your code may die without telling you:

    • You may set a database object to null (to be tidy) but later try to run a transaction or SQL command.
    • Make a syntax error when you set up an error callback for a function, and obviously it won't be called back...but you won't know.
    • I once broke my whole app by forgetting to define one argument in a function called upon a native event.
    • Javascript's prompt() function is not defined (jQuery has stolen it!), even though alert() is.
    • You can often get away with self-closing tags such as <div id="blah" /> but do remember this isn't legal in HTML5 and sometimes your javscript will mysteriously fail!
    • Database error callbacks (for statements) need to return true or false, depending on whether you want the transaction's error callback to be hit, or call the success callback regardless.
    • You need to think asyncronously whilst coding. More on that below.

Asyncronous code and callbacks

Calls to the API to access the device's features such as GPS, database etc are asyncronous. If, like me, you've spent two decades writing basic Javascript for pretty web pages, or writing standard ASP.NET, then this may feel a bit odd. If you want to get some strings from a database, you can't just write the function call, and then put your next bit of code on the line below. Calls to .executeSql(), for example, will kick off a seperate thread, and the line below will be executed immediately, probably way before the database has been accessed. You have to use callbacks, which felt odd to me for a while.

Some basic code snippets

Don't use Javascript's .alert() function any more. See samcroft.co.uk/2012/native-notifications-for-phonegap-cordova-apps for details of how to write popup warning boxes that look nicer. Example:

navigator.notification.alert('Full details blah', null, 'Error!', 'OK'); //message text, callback function, title, button text

To see what OS you're dealing with, and to get a unique identifier for the device, you can use the code below as long as your config.xml includes the line <feature name="http://api.phonegap.com/1.0/device" /> somewhere in the middle:

var message = 'You are using ' + device.platform + ' and your UUID is ' + uuid:device.uuid + ' today.' //the UUID may not stay the same on some devices

If you make an ajax request to your live web server, to get some data and return it to the mobile app, you may find it useful to provide some error handling. The .fail callback of $.ajax (I'm using jquery mobile) is passed two arguments, let's call them jqXHR and textStatus. I've found the latter to contain just the word "error" but the former is more useful. jqXHR.responseText contains the actual HTML sent back and jqXHR.status is a number - the HTTP response code. For example, if I make a mess of the ASP/PHP/.NET page which is called on the live server, and it errors, then the status is 500. A useful trick is to check for zero - this means the device is offline (e.g. a tablet that is disconnected from wifi, or a smartphone that can't connect with 3G at that time).

You can't get your own version number at runtime without a plugin. This would be useful when you've uploaded 50 different ZIPs to the Build website and would like to display your version whilst testing. On a device you can always look at the settings for an app (it lists how much memory it is using, and also displays version details.

Debugging in a browser

I run my code in Chrome before I bother compiling and installing on a mobile device. With your local copy of phonegap.js present you can make calls to the database API, though a web browser will not provide any accelerometer data! There are browser plugins which may help with this. Some can be configured to provide a lat/long to your app, as if coming from a GPS device.

I found my code halting because navigator.notification.alert did not exist in Chrome. You can fake this by setting up your own javascript object/method for use when testing, but don't leave this in place when you upload to Phonegap Build. Here's a simple example to view notifications in a browser:

var navigator = {};

navigator.notification = {};

navigator.notification.alert = function(a, b, c, d) { alert(a); }

Sneaky caching tricks

When writing an app that allowed a person to look at photos even when offline, I researched various ways of loading images from a live web server into the mobile device's cache. They all need complex API calls and 20 lines of code. I realised that just writing a <img> tag into your HTML meant the image would be cached, so once someone had used the app whilst online, the photo would still be displayed if they used the app later when offline. So how could I cache the 50 images that I wanted to get? As I was in a massive rush, and new to all this, I decided to be lazy. I slapped a tag into my HTML inside a hidden DIV that was not displayed to my users:

<img id="getincache" />

I then wrote a loop in my JS to load the images I wanted (in reality I got a list of these from my live web server via ajax, but this example uses numbered images):

for (var i = 0; i < 50; i++) {

$('#getincache').attr('src', 'http://www.blah.com/photos/' + i + '.jpg');

}

Then if my code were to display image tags that used these same URLs later, they would appear regardless of the device's online/offline status.

Quick and dirty, but it worked. Doubtless there are terrible implications of this method...but I haven't seen them yet. Honestly, I was in a terrible rush to make a prototype (not a finished app) so considered it fair play.

UPDATE: with a later version of my app, this trick stopped working. I haven't yet found what is to blame.

With images from the web, you do not need to worry about clearing the cache yourself, to save your app hogging too much memory. The images are stored in the browser's cache, not your app cache.

Do not try super.clearCache() which you might see if you hunt around via Google. You cannot just slap this into your Javascript.

Databases in mobile apps

You can store simple strings/numbers in the file system if you want, but a database is much easier for complex apps, and is supported across more devices. Make sure your config.xml includes the line <feature name="http://api.phonegap.com/1.0/file" /> somewhere in the middle, so you have permission to make these sort of API calls. When people download your app, they will probably be told that the app will write to 'USB storage' or something like that. You can then create a database according to a spec that the W3C created but no longer support. You can write 'SQL lite' commands to access the tables.

SQLite is not proper SQL

You can set up a column of type DATETIME in your table, but this won't stop you putting "hello mum" in there. Apparently it is up to you to manage the format of the contents. At least there are some built-in date and time formatting functions. See www.sqlite.org/lang_datefunc.html and www.sqlite.org/datatype3.html for more information.

Here are some examples of valid SQL statements I have used in my app:

CREATE TABLE IF NOT EXISTS NewsArticle (ArticleId INTEGER NOT NULL PRIMARY KEY, DateAdded DATETIME, Title VARCHAR(50) NOT NULL, ArticleText TEXT)

DELETE FROM NewsArticle WHERE DateAdded < date('now', '-7 days')

SELECT ArticleId, strftime('%d/%m/%Y %H:%M', DateAdded), Title FROM NewsArticle ORDER BY DateAdded DESC LIMIT 250

SELECT MAX(DateAdded) AS NewestDate FROM NewsArticle

SELECT ROUND(JULIANDAY('now')) - ROUND(JULIANDAY(DateLastLoggedIn)) AS DaysSinceWeSawYa FROM AppUsers --similar to DATEDIFF in normal SQL

SELECT * FROM NewsArticle WHERE ArticleID = :id

INSERT INTO NewsArticle (ArticleId, DateAdded, Title, ArticleText) VALUES (2, '2012-09-21 14:00', 'Test 2', :blah)

The last two queries have parameters using a colon. The values are passed within a Javascript array as the second argument in the .executeSql() command. Or you can use question marks to refer to the array elements by the order in which they appear. See example below or read www.raymondcamden.com/index.cfm/2012/1/6/Working-with-dates-and-SQLite-in-PhoneGap

var dog = 'hello';

var cow = 123;

tx.executeSql("UPDATE mytable SET mycol = :cat WHERE myrow = :mouse", [dog, cow], null, update_error);

If there were no parameters the array would be [] or null.

Notice the names of variables and parameters bear no relation to each other; it is the order in which they appear that matters. Getting things wrong may lead to a message such as "number of '?'s in statement string does not match argument count" even if you were using colons and no question marks.

I made the mistake of using ISNULL() thinking it was pretty standard and safe; SQLite has IFNULL() instead - see www.sqlite.org/lang_corefunc.html

Error codes returned when executing SQL statements

    • UNKNOWN_ERR = 0
    • DATABASE_ERR = 1
    • VERSION_ERR = 2
    • TOO_LARGE_ERR = 3
    • QUOTA_ERR = 4
    • SYNTAX_ERR = 5
    • CONSTRAINT_ERR = 6
    • TIMEOUT_ERR = 7

Functions referenced in .executeSql() will be called back in the event on an error, and passed an error object and a transaction object. The latter only has the .executeSql() method available (I was hoping it would tell you the SQL it had just tried to execute!) but the former is more useful with .code and .message properties.

You can either check the numerical value (see my original question above) or use something like:

if (error.code == error.DATABASE_ERR) alert('nasty database error')

The .message property is a string and may return something like this:

  • could not prepare statement (1 near "wibble": syntax error)
  • could not prepare statement (1 no such table: MyyTable)
  • could not prepare statement (1 table MyTable has no column named MyColunm)
  • could not execute statement (19 constraint failed)

Other messages are possible! This is just the few I spotted when debugging in Chrome/Android. I notice in Android the messages are briefer: "no such table: MyyTable"

UPDATE: I was storing SQL ststements in a file, and looping every line to run them against the database. But I added some comments to the file! I found out that trying to pass a SQL comment (with no executable query) to .executeSql() will give you an error code 0 with the helpful error text "not an error".

Publishing on Android - Goole Play Store

This was a remarkably easy process. Anyone can publish an app in the Android store by registering as a developer for $25. You upload the APK file and fill in a few boxes. Prepare some screenshots, icons, and other graphics in the dimensions Google asks for, and you're done.

The only complication was signing your APK file. You need a certificate/key for this, which can be created on your PC if you have the latest Java version (or use Mobundler to make life really easy).

Open a command prompt (ahhh, DOS!) and enter this:

keytool -genkey -v -keystore whatever.keystore -alias wibble -keyalg RSA -keysize 2048 -validity 10000

The filename will be whatever.keystore and you'll need a note of the alias you have chosen (wibble in this example).

You'll then get asked a series of questions:

  • Enter keystore password:
  • Re-enter new password:
  • What is your first and last name?
  • What is the name of your organizational unit?
  • What is the name of your organization?
  • What is the name of your City or Locality?
  • What is the name of your State or Province?
  • What is the two-letter country code for this unit?
  • Enter key password (or press RETURN to re-use keystore password):

Note is is asking you for two passwords, though you can make them the same. Make sure you know which is which as the descriptions ("keystore password" and "key password") are not very distinctive. I have no idea if Google ever use your name and location information from the questions above.

Previously the Phonegap Build process offered you file to download that ended with "-debug.apk", but use the website's settings page to untick the debug option, and you'll get a "-release.apk" file. Then upload your whatever.keystore file and then website will ask for two things: a name (this can be anything, this is just used to list it online) and an alias (which needs to match the value you used when creating the key, which was wibble in my example). You can delete unwanted keys from your account here: https://build.phonegap.com/people/edit but be wary of this, as you may need them up upgrade your app in the future (see comments below about keystore file, alias and password).

Phonegap will then rebuild your APK with the signing key, and you can download this file, to upload in the Android developer console. After the upload, the page will confirm the app version, or reject the file if it is not signed. It will take between 2 and 24 hours for your app to appear in the store. Upgrades might be faster than new apps.

Note that upgrades to your APK must be signed with the same certificate as you used when you submitted the first version of your app. So when you first add an app, keep your .keystore file safe, and keep a note of the alias you used, and the password(s). Nothing can help you if you forget the password, but forgetting the alias is not so bad. Open the file using Windows Notepad (or similar text editor) and you will see the alias as plain text near the start of the file. Remember I used wibble in my example above. You'll also see the answers to the six questions later on in the file, as plain text. No, the password won't be visible in any way.

Publishing with Amazon

Sign up for their Developer console and it is easy to publish the same APK you used for the Google Play Store above. I recently upgraded an app, and Amazon inspected it and made it 'live' within 2 hours.

Publishing with Apple's app store

Things are move complex if you want to sell your app for iPhone or iPad. You need access to a Mac, for the final stage (just for a few hours, probably on two occasions). You can't even read some of the documentation until you've paid money to enroll!

First step is enrolling as a developer. Register for an Apple ID (free, just needs an email) first. Even filling in the developer form is a pain as you tell them you live/work in the UK, and they want a US phone number and a DUNS number (each business in the US has to have a unique numerical code, apparently). You can get a DUNS number for free I believe. Try http://salesmarketing.dnb.co.uk/find_my_company/ and wade through the blather. After registering Apple may phone you up to check your identity/intentions. Finally you'll be permitted the pleasure of paying them $99 or £60 (annual renewal only £45). Eventually you'll get an email saying your account is ready; you can now read the documentation and start to add your app to the App Store.

You'll have noticed that the Phonegap Build website never offered you an iOS download whilst you were developing/debugging your app. Compiling for iOS needs a certificate. Apple and Phonegap often say you have to create the certificates on a Mac but it is possible to use OpenSSL on Windows. I originally saw the complex instructions here http://community.phonegap.com/nitobi/topics/detailed_guide_for_setting_up_building_ios_apps_without_a_mac and here http://nickalchemist.wordpress.com/iapproach-mobile-development-made-elegant/deploying-to-iphone-without-a-mac-with-an-apple-developer-account-and-phonegap-build/ and ran away crying, to use an online service instead (Mobundler, sadly now defunct). I cannot find any other online providers who will generate all the certificate files, and have since found a friend with a Mac who uses a program called Keychain Access and sends me the files it creates.

For testing:

Find out the device id (40 character hex UUID) of each iPhone/iPad/iPod touch that you want to test on. This is shown in iTunes if you plug your device in and go to settings/summary. Or get a free app that tells you.

Back to the Apple site, and set up these UUIDs in "Devices". If you haven't already done so, pick "App IDs" and create one. Next you pick "Provisioning" and create a development provisioning profile which contains a reference to three things: your certificate, your app ID, and your devices (make sure to tick them all). Once that has been created, you can download the .mobileprovision file.

For going live in the store:

You perform similar steps to the above, but instead of talking about "developement" you are now talking "distribution". Create a certificate for distribution, and create a profile for distribution. The only thing you don't need is the device UUIDs. Again you end up with a .mobileprovision file.

Phonegap Build will ask for your .p12 and .mobileprovision files in order to create a key (it doesn't matter what name you enter). You can then click to unlock this key (with your own password), rebuild the app, and finally you are able to get the coveted .ipa file that runs on iOS. You can delete unwanted keys from your account here: https://build.phonegap.com/people/edit

Test a development version of your app on your own iPad or iPhone by:

    • using a barcode scanner app and holding your phone up in front of the Phonegap Build website where it shows the QR code
    • using your phone's browser to go to your app's page on the Phonegap Build website (not logged in) where it will offer a button saying "IPA" you can click on
    • copying the IPA file into your iTunes apps folder, and syncing.
    • using a free TestFlight account

If your iOS device says it cannot install your app (when testing) then check if your certificates are still valid. Expired certificates will cause the install to fail without any helpful messages. Same thing will happen if you pick the wrong combination of .p12 and .mobileprovision files on the Phonegap Build website. It won't tell you that they are wrong.

When you are ready to go live in the App Store, untick the Phonegap Build debug option, and upload your 'distribution' profile/certificate to compile the final IPA file. Go back to iTunes Connect (your $99 gives you access) and fill in details about your app here, but you can't upload the IPA online. You have to install some software called 'Application Loader' (Mac only! see below) to do it. It will then take around 14 days for your app to be approved and appear in the store.

Application Loader

This is a Cocoa program made by Apple you can install on a Mac here. I have not found a way send an .ipa file to Apple without using it. Before you use it, make sure you have created an entry for your app in iTunes Connect and that it says "waiting for upload" as the main status message (little yellow light). You'll be asked to enter your company name, app name, blurb, and provide a screenshot for every device you support (usually iPhone/iPod 3.5" screen, iPhone 4", and iPad). Pick pricing options and so on here as well. You can fiddle around with this for days before you need to actually send then app.

If the above steps are done, when you provide your Apple ID (email/password) to the Application Loader program it will be able to see your list of apps. Don't use the "open package" button at the top, as this will look for a .zip file. Use the middle "delivery your app" button which lets you pick a .ipa file, and then shows your apps in a dropdown. You click through a few steps and off it goes! Once finished you can return to iTunes Connect and see your app's status is now "waiting for review". You will get email comfirmation. Now you wait a week or two....

UPDATE: Occasionally some Macs will return an error, suggesting there is something wrong with the file. The error message says something about installing XCode. This is not an error on your part! It is worth trying a different Mac (as I did) and finding that the same file works perfectly.

Publicity

I created a tiny HTML file containing some simple javascript, hosted on our own web servers. I created a free QR code at http://goqr.me/ to link to it. If someone visits the link from a normal browser on a laptop, say, they will be redirected to another page on our site which talks about the app features. If an Android user scans the QR code, they get redirected to the Google Play Store. If an iPad/iPod/iPhone user scans the code, they are redirected to the App Store. In both cases the device goes direct to the entry for your app, and people can install straight away.

if (navigator.userAgent.toLowerCase().indexOf("android") > -1)

window.location.href= "market://details?id=com.mydomain.myappname";

else if (navigator.platform.indexOf("iPhone") > -1 || navigator.platform.indexOf("iPod") > -1 || navigator.platform.indexOf("iPad") > -1)

window.location.href= "itms-apps://itunes.apple.com/us/app/myappnamewithhyphens/idmynumericappid";

else

window.location.href= "http://www.mydomain.com/blah.html";

You need to change the bits in red! The Android Developer Console will remind you what your 'package name' is. iTunes Connect will tell you what your 9 digit 'App Apple ID' is, and you can look at the URL of the link entitled "View in App Store" to see what the alphameric name is (they replace spaces with hypens). An example would be https://itunes.apple.com/us/app/grumpy-pigeons/id123456789?ls=1&mt=8 and you just change the leading protocol and remove the querystring.

I'm no expert but I hope this helps,

Magnus Smith