Article version of this PhoneGap Day EU presentation given in September 2012
Summary: WebKit browsers are capable of a lot, but writing native code is still necessary for certain things. Hence: "hybrid apps". The web is a double edged sword. Openness on one side and splintered bugginess on the other. Most of the bugs I've encountered have been squashed in ViewKit, a jQuery mobile-esque UI library designed for mobile WebKits.
This is part two of a series, the first part is called Fast WebView Applications.
My company (@gather) is a little under a year old and consists of 2.5 (two full time, me and @mikeal, and one design contractor, @objcts). Mikeal does all the backend... node, redis, couch, apple + google push notifications, load balancing, deployment, etc and I work on our mobile app. As covered in part one, we rolled with PhoneGap due to a) front end team size of 1 (with little to no iOS or Android experience) and b) a somewhat dogmatic belief in the open web and c) cross-platform.
So far mobile development has been an uphill battle. Not as bad as IE6 but it approaches the same level of facepalm. Here are some example commit messages to give you a sense of the day-to-day reality of cross platform development:
If you aren't already a super capable client side developer I would definitely not recommend attempting to make a high quality hybrid native app today. The community just isn't there yet. If you buy an iOS book you learn about the built in UI libraries and Interface Builder, have world class documentation, tons of google-able error messages and stack overflow posts, and generally lots of great example products in the App Store to draw inspiration from. PhoneGap gives you a blank white browser window and you take it from there. Fixing cross browser bugs, the entire UI for your app and custom native plugin functionality are all left as exercises for the reader.
It isn't that these things aren't possible. As HTML5 gets better and better these problems will disappear. But the state of mobile app development today is that the current JS UI framework offerings just don't compete with the UX of the native smartphone SDKs.
So if it is a pain, why are we doing it this way? The redeeming quality of the web is that the more work you pour into it, the more value you create in the ecosystem. Things like Firefox OS, which will allow mobile app developers to fork the operating system, are going to be huge wins. We just happen to be among the first on the bandwagon :)
At the moment, our app has one set of front-end assets that run on most iOS (5.1+) and Android (2.3+) devices. We use native plugins to do only a few things: popup browsers for the OAuth login dance, date + time pickers for forms and push notifications. Our entire UI is just one big web view.
A few months into development I realized that I needed to abstract out a few of the UI components into a library so that I could make sure everything worked in all of the browsers that we aim to support. This library is called ViewKit and can be thought of as a WebKit specific jQuery mobile with way less features.
All in all we have five repos: ViewKit, our app specific front-end code that uses ViewKit, Android and iOS projects (that both symlink to the same front-end code), and our backend.
At the moment the only way to use Gather is to install the native app. We want to release a web-app version but haven't had enough time to do enough testing to feel good about a more wide release like that. The cool part is that when we are ready we would just serve the same application code in the above diagram directly from a static file server.
The majority of the app was written on my laptop in Chrome + its dev tools, the same workflow I've been using for years. Our designer, Michael Felix (@objcts) worked with us during the initial conceptual phase and then starting whipping up some pixel perfect PSDs.
On device testing is a must. Get a new Android + iPhone and an old Android and iPhone. We went with the Galaxy Nexus, iPhone 4S, some random LG android phone that runs 2.3 and an iPhone 3GS. Turns out nobody has really complained to us about iPhone 4.x compatibility so we are currently iOS5+6 only. Android emulators are a waste of time, just plug your device in. iPhone Simulator is nice though. With PhoneGap nowadays you can do pretty much everything from the command line:
// get phonegap
git clone https://github.com/apache/incubator-cordova-ios.git
cd incubator-cordova-ios
// create a new XCode project
./bin/create ~/src/gather-ios com.gather.gather Gather
cd ~/src/gather-ios
// compile app
./cordova/build
// run in simulator
./cordova/emulate
// get an .ipa for ad-hoc (testflight) or app store distribution
gem install shenzhen && ipa build
or for Android:
// get phonegap
git clone https://github.com/apache/incubator-cordova-android.git
cd incubator-cordova-android
// create a new Eclipse project
./bin/create ~/src/gather-android com.gather.gather Gather
cd ~/src/gather-android
// generate an .apk and run it on a plugged in phone
./cordova/BOOM
On app load we first execute all of the common, platform independent JS and then do a bit of UA sniffing (using zeptos $.os
plugin) to load browser + platform specific scripts. These scripts include things like the required cordova.js
library (which at the time of this writing has specific builds for specific platforms), our phonegap plugin JS files, and any browser specific polyfills. We also have a iOS specific stylesheet that currently has a single line to enable -webkit-transform
animations (they are disabled by default because Android animation performance is abysmal).
There are a few other places in the app where we have platform specific code. Here is a snippet from our date picker code that executes after you choose a date or time (or both) in the native date picker UI popup dialog:
if ($.os.ios) {
$('.datepicker').text(moment(start).format("MMM Do h:mma"))
} else {
$('.datepicker').text(moment(start).format("MMM Do"))
$('.timepicker').text(moment(start).format("h:mma"))
}
iOS has a combination date + time picker in a single dialog but Android offers only date or time but not both, so we have to add a new input field for Android users and branch a few times in the code to handle both platforms correctly.
Since the app is just a WebView I just use XHR for most things. We wrote a RESTy JSON API in node for doing things like authentication, retrieving user data and posting new data, etc. Since our API has CORS turned on I can use Chrome on my laptop to develop directly against our production servers over SSL to make sure everything works. This speeds up development workflow dramatically because mobile debugging is a pain.
The latest (and unfinished) thing we've been working on is a real time chat in the app built with socket.io, a popular cross-browser node powered real time abstraction. In my experience so far socket.io is a bit too monolithic and ends up being a pain to debug and configure. It doesn't implement things like the node Stream API but instead has it's own connection state API and automagic reconnect logic. When it works it works great but when it doesn't you have to ask the socket.io team for assistance. The next version of socket.io (currently in development) is supposed to fix lots of these issues but it isn't done yet :(.
In order to actually transmit data over websockets on mobile there are a couple of requirements: a) wss://
is a must because normal ws://
messages sometimes randomly get lost by poorly implemented mobile carrier proxies, b) you have to be able to handle the wss://
encryption overhead on your server (equivalent to https://
). In node right now SSL is 10X slower than non-SSL so this can be a deal breaker (SSL speed is a priority for the node team but OpenSSL is hard).
Due to the limited wss://
support in mobile browsers (pretty much only iOS) we have just been forcing socket.io to use its XHR polling transport because XHR works everywhere. If I had to re-write the whole chat feature again from scratch I would probably just use a custom XHR polling transport or shop around for something way more focused and simple. However, socket.io will be awesome when we release a webapp version of Gather because ideally we wouldn't have to worry about as many cross browser + desktop issues. Both solutions have tradeoffs. I'd love to hear others experience with the open source real-time web on mobile!
This might sound fancy but it just means that you should make your web app look good on different screen sizes. I employ three techniques with Gather to make sure the app works on all the random Android phones it gets installed on: fluid css (for slightly different screen widths and heights), sprites (for different pixel densities) and responsive design/media queries (for drastically different screen widths and heights). I'm not gonna go into detail here (since there are tons of better sources for info on these techniques) other than on two points: css flexbox
is awesome (even though the spec is in flux) and glue is super great for creating sprites.
We don't officially tablets yet but the app is at least somewhat usable on them without any modifications -- the buttons are just kind of small. It wouldn't be that much work to put in a media query and tweak the css, I just haven't gotten around to it.
As I mentioned earlier, there are a few places where we use PhoneGap plugins. Plugins are designed so that they expose a single JS API and then offer a number of platform specific native code implementations. That way you can write your JS once and then use the .java
plugin for Android or .h
and .m files for iOS. Another awesome thing about PhoneGap plugins is that it is a way to prototype experimental W3C APIs via native polyfills. The date picker shown above is a native plugin.
For bringing up pop-up browser windows we use a popular 3rd party PhoneGap plugin called "child browser". This plugin is soon changing its name and moving into PhoneGap core. On iOS I wasn't happy with the default child browser UX so I wrote a API compatible alternative that is based on the UX of the iOS Facebook SDK (pictured below on the right). It's available here. The picture on the left is the same JS API running on Android to generate a web browser overlay written in Java using the Android SDK.
Push notifications are one of the biggest reasons we aren't a web app. There are some nice plugins for both iOS and Android to handle registering devices for push notifications and getting device tokens than you can send to your server and use to send push notifications later. At the moment the APIs are different between the two platforms but there is some work happening now to standardize the JS API for registering a device and receiving a push notification across all platforms that offer push notification functionality. There is a plugin for Android called StatusBarNotification that lets you send messages to display in the Android status bar (as pictured below on the left). The JS API actually polyfills the Web Notification API from the W3C, which is a big win for future proofing our app.
Submitting to the Android Play marketplace is pretty straightforward and once you upload your app it only takes between 30 minutes and 6 hours for it to become available for download. iOS, on the other hand, usually takes 1-2 weeks for app review and involves an insanely complicated certificate signing and device provisioning process. Be prepared to search stack overflow for obscure XCode generated errors and find answers that tell you to delete your entire XCode project and start over from scratch.
We've been using Testflight for beta testing. It is worth checking out and simplifies the process of distributing beta builds to authorized beta testing devices (that you have to manually add to the Apple developer portal). Unfortunately there is no API for a lot of the Apple developer portal so there are a lot of manual processes involved in distributing iOS apps.
There are two things that aren't here yet but are super exciting. One is the ability to programmatically install native plugins in the same way that you can type `npm install foobar` to get modules in node. The PhoneGap team is working on this and it will make development way easier, as well as enable the PhoneGap user ecosystem to really blossom (just look at what NPM did for node).
The other thing is doing remote updates. Since PhoneGap apps are just a bunch of dynamic code you can easily write a Google Chrome style autoupdating capability into the apps as a way of circumventing Apples crazy long review process. There are definitely guidelines from Apple that you can't break (bait and switch -- changing your apps UI so that it suddenly shows naked people), but for things like fixing JS bugs and making minor updates to libraries, templates and stylesheets this kind of remote update functionality is going to be super awesome.
Don't use PhoneGap if you aren't willing to get down and dirty and help establish some good patterns for hybrid mobile app development. It is still the early days. But the idea that you can use the open web stack to write nice mobile phone apps isn't crazy. After going through the process I would say I did about 80% web, 10% iOS and 10% Android. Would I have been able to achieve the same goals with me doing 50% iOS and 50% Android? I couldn't tell ya! I'm just happy that I got to keep writing JS without sacrificing too much in terms of quality.