We are regularly approached by potential clients who want us to help them build or maintain mobile apps. Many are interested in hybrid apps : HTML5 web apps that run in an embedded browser, with most common code across iOS and Android. Typically, this use a framework such as Cordova (a.k.a PhoneGap) which wraps a web app up for distribution and provides access to some device-specific APIs.
The apparent cost savings of these apps are obvious, especially if your organization already has the skills to develop web sites. And as developers, we’re all about saving ourselves effort and not writing everything twice.
Our experience, however, is that hybrid apps carry hidden costs that are not immediately obvious. At Zuhlke Engineering Limited in the UK, we now advise our clients that, in virtually all cases, it’s better to build native versions of the app for each platform you want to target.
This doesn’t mean all hybrid apps are bad, or that it’s impossible to build a good one. For some use cases, hybrid apps are a good fit, but this is the exception rather than the rule.
First impressions count
For a mobile app, user experience is everything. The App Store and Google Play store make it convenient to try things out. If someone tries your app and it takes more than a few seconds to start, or has an unpleasant or sluggish user interface, they might leave a bad review, or (worse still) just try something else.
iOS has extensive Human Interface Guidelines , and Google has similar guidelines for how an Android app should look and work. Users expect the apps on their phone to use the standard UI patterns, like the rest of the apps on their phone. For example, to go “up” in an information hierarchy on iOS, you swipe from the left edge; on Android, there’s the physical ‘back’ button, but no swipe gesture. If you’re building a web app from scratch using Cordova, you may have to implement behaviours such as this yourself, for both platforms.
You can save yourself some work with a framework such as Ionic, which bundles Cordova with Angular, some stylesheet, extra build tools, and some components that render appropriately and have the expected behavior on each platform. But these can be hard to customise when you need more precise control over behaviour or appearance.
Alternatively, you could decide you don’t care that the app looks the same on every platform. But this might only be acceptable in a few circumstances. In most cases, if you’re deploying your app to the public, you can expect your users to notice that something’s off, or get confused by unfamiliar UI patterns.
When it comes to performance, Apple suggests that a good launch time target for an iOS app is 400ms on the slowest supported device. If your web app uses Angular (which is bundled with Ionic) your users will have to wait for that to load, and this can take a long time. In some cases it can lead to launch times of five seconds or longer on older IPhones, and even worse performance on Android.
Animations and interactions are also slower when implemented as CSS inside a web browser than when implemented natively. Pages might flash when loading. Even scrolling behavior is different, and usually more sluggish. You might decide that this is an acceptable trade-off – but your users will notice.
The situation is worse when it comes to supporting phones’ built-in accessibility features. Native components are accessible by default, and VoiceOver and Talkback behave predictably around them. Web components, especially custom modals, menus, and alerts, do not. You may well have to re-implement the logic to trap focus inside modals, move the screenreader cursor to the correct element, etc. yourself. You may decide this is an edge case that you do not care about, but accessibility is usability, and if your app is being released to the public, it’s not unreasonable for disabled people to expect your app to support the features they rely on .
The shared-effort fallacy
A common motivation for hybrid apps is only having to write the business logic and user interface once, without needing expensive iOS and Android expertise.
Sometimes a feature will exist on one platform, but not another. The result is often a codebase that is full of per-platform code: “if iOS, run this long, intractable code block; if Android, run this other, subtly different code block.” It looks like two separate code bases merged into one, because it is.
Still, if “write once, run everywhere” works 80% of the time, surely it saves 80% of development effort, and reduces costs by 80%? This only works if you ignore the costs of testing . Unit tests are worthwhile but, when it comes to mobile apps, it’s even more important to test on real devices-especially on Android, where different phones have wildly diverse performance and feature sets.
It’s worth noting that the iOS and Android web browsers have subtle quirks and differences. While it sounds nice to be able to write one UI in HTML and CSS that renders anywhere, those of us who’ve been in this game long enough to have to worry about supporting Internet Explorer know this is a chimera. The quirks in the different platforms’ browser implementations can lead to platform-specific bugs which you may not know about … until your users report them.
An annual barber’s appointment for Mr. Yak
Mobile platforms move fast. iOS now receives a major update every single year in mid-September. Major Android releases happen every year too, although hardware fragmentation means that uptake for new Android releases is significantly slower. With these releases come new features, which may be supported ‘for free’ by the platform; new releases also bring deprecations, and breaking changes to the APIs.
Your users will expect to be able to update their device and for your app to continue working immediately. This is why Apple and Google release betas around three months in advance, to allow developers to test and iron out any incompatibilities well in advance of the new OS’s general availability.
In mid-July 2015, a bug was raised against Ionic and Angular on iOS 9 beta 3. A regression in
window.locationimplementation Caused Angular apps to end up in an infinite digest loop, effectively breaking navigation. A workaround for this bug was provided by the Angular team on 11th of September, two days after the iOS 9 Golden Master was released, when it became clear that the
UIWebViewbug wouldn’t be fixed. In the Ionic team’s blog post on 12th September, they wrote :
there’s time to make the iOS 9 roll out if you test, fix, and submit ASAP […] Unfortunately, we assumed these glaring bugs would would be fixed in time but they were not.
Given that the App Store review time could be up to a week, some developers using Angular and Ionic in their hybrid apps weren’t able to push fixes in time. What’s interesting here is that the regression wasn’t caused by Angular or Ionic, but in iOS itself. But from a user’s perspective, it made no difference. The apps were broken on iOS 9.
At the time of writing, in mid-December 2017, a newly-generated Cordova app generates 11 deprecation warnings when opened in Xcode. Plugins can add dozens to this figure. And Cordova still does not support the iPhone X’s edge-to-edge display out of the box, over six weeks after it was released.
In a world where hardware and software moves so fast, is it sustainable to rely on a complex web of dependencies to ensure your app looks nice – or even work at all – on the newest platforms and devices? The dependencies that matter the most are the operating systems and their APIs.
Thirteen amps and three square pins
Most of us who’ve travelled internationally will have a menagerie of power adapters at home. My own electronics scrapyard has at least three UK-to-mainland Europe adapters (only one of which fits hexagonal Swiss sockets) along with a couple of American adapters.
A lot of the cheaper adapters on the market are awkward to squeeze UK plugs into, because of small violations of the BS 1363 standard. Some are loose, which is even worse because of the risk of exposed contacts. At best, this can lead to a tenuous power supply; at worst, it can lead to damaged equipment, fire, and electrocution.
This may be acceptable if you need to charge your phone in a hotel room for a few nights. But it’s a hack. If you were the facilities manager for a British company expanding to new offices in New York and Amsterdam, you wouldn’t populate their kitchens with kettles shipped over from Britain with plug adaptors thrown in. You would fit the kitchens out with appliances bought locally.
This is the problem with hybrid apps. They are a hack. A hybrid app will usually work, just about. But they typically provide a sub-par user experience compared to native apps, and less flexibility than comes with just building a website. This might be a trade-off you’re willing to make to get your logo in the App Store and on to your customers’ home screen, but you must decide if you want them to associate your brand with a mediocre user experience.
And then there’s the problem behind the scenes: your complex web app is encased in a hybrid app framework, which is itself encased in a complex scaffolding of native code. The whole stack appears like one of the contraptions in the Wallace and Gromit films, supremely intricate and impossible to understand or maintain. At this point, your team needs more than generic web skills to progress. You might as well have written a native app.
The temptation of familiarity and reduced effort is obvious. But for anything remotely complex, development can spin out of control into yak-shaving or painstakingly trying to reproduce weird rendering bugs. I’ve been that person too many times to recommend hybrid apps as a good mobile strategy.