Lessons learned from 4 years of using Cordova in production

I spent four years developing an application for iOS and Android. It ended up relying on about 20 Cordova plugins, and developed a long list of capabilities:

  • Showing maps with complex overlays (even Web GL maps in the latest version)
  • Reading/writing local files
  • Using camera for photos and QR code scanning
  • Working in the background
  • Continuously tracking the user's position, including long stretches of time in the background
  • Using the accelerometer to detect motion stop/start events
  • Tracking Bluetooth beacons (ranged continuously in the background)
  • Local notifications
  • Push notifications
  • Uploading images to S3
  • Working offline
  • The usual interaction with a server to sync up data

I think it pushed the boundaries of what's possible to develop with Cordova.

I really appreciate Cordova, and it was the only viable option for my (former) business to develop an application that could run on two different platforms, and additionally share code with another web application. But realistically, it has a lot of drawbacks as well.

Leaky abstraction

Cordova is very much a leaky abstraction, and there's no way you will forget about platform specifics and develop a single glorious JavaScript codebase.

I couldn't fully escape native code as I ended up modifying native code in the plugins to achieve the desired behaviour or implement fixes.

Some things required hunting for additional platform specific plugins. For example, to get my application to run in the background on Android, I had to add an extra plugin that would allow me to configure the appropriate wake lock.

From time to time, I had to find and install tiny single purpose plugins, eg to fix an Android build error caused by missing translations prescribed in the default configuration, or to add new settings required to use the camera on a particular version of iOS.

A small community with limited resources

Cordova seems to have a small number of contributors (although they appear to be quite dedicated!). Things will generally lag behind OS releases; plugins are likely to be updated once the OS has been out for a while.

There are bugs in the tools, and bugs in the plugins, and a lot of things generally feel slapped together and unpolished. Bugs can stay open for a long time, particularly in plugins.

A couple of issues I had that highlight this:

  • At some point, Cordova screwed up the version codes for Android by appending zeroes, causing quite a few headaches for developers as their version numbers started approaching the upper bound of the range. Cordova has been fixed, but as the version number has to increase monotonically from the point of view of the Play Store, I had to add an extra build step to append zeroes to the version code during the build process.

  • There are issues with how plugins request permissions from the OS. If there is more than one plugin requesting a particular permission, it's either going to cause the build to fail, or one of the requests to win out (based on an unknown order). When a randomly ordered request wins out, it's still a problem because of the user prompt messages associated with requests on iOS. Since I wanted the prompts to have particular text, once again, I had to add an extra build step to set them later on in the build process.

Limited capabilities

Cordova is limited both by having to be "the lowest common denominator" for multiple platforms, and by being an abstraction on top of native APIs. If you try to do more complex stuff, you will inevitably run into various limitations and bugs.

For example, I had a lot of problems with developer tools on iOS not working when I started to integrate Mapbox's Web GL maps into the application.

As time goes on, the web view itself is becoming more of a risk on iOS. Apple released WkWebView as a replacement for UIWebView in iOS 8. WkWebView provides much improved performance. It can be used in Cordova apps, however it comes with a number of caveats.

Critically, it doesn't run JavaScript when the app is in the background (which worked with UIWebView). It also requires your server to have CORS enabled so your app can make asynchronous requests when served via file://. These limitations are largely imposed by Apple's implementation, and it's not clear either whether they are going to be resolved, or how long UIWebView will continue to exist.

There were other oddities, like weird errors when trying to read local files, and inconsistent reporting of the connection status (for example, on some Android devices connected to WiFi in guest mode). The power status and battery level couldn't be retrieved on demand, but would only be reported upon some change, which meant I was in the dark if the device happened to be hooked into AC power and 100% charged - no battery events would be generated in that case.

Non-native UI

When I started this project, there were hardly any viable options for cross-platform mobile development. Initially, I started working with a platform called Steroids which promised to provide elements of native UI (e.g. transitions, a navigation bar) in combination with Cordova for the rest of it.

However, I quickly realised that I was getting mired in a leaky abstraction hell. The combination of a complicated framework (Steroids) on top of another framework (Cordova) was lethal, with difficult to isolate bugs. I made a decision to migrate to plain Cordova which, luckily, was straightforward at that point.

These days there are multiple options for cross-platform mobile development with native UI:

  • React Native
  • Xamarin
  • NativeScript

While they probably manage to avoid issues specific to running most of the app in the web view, they still have a lot of the same caveats as Cordova.

Impossible to Track GPS in Background on iOS

After a long time spent trying to find a solution to this, I had to conclude that there is simply no way to reliably track GPS locations in the background on iOS for extended periods of time. Something appears to have changed internally starting with iOS 8 (or possibly even iOS 7) to cause this. While some devices will work without fault for weeks (developer devices are always in this subset of course!), on others the app gets either suspended or terminated sooner or later.

It's a difficult issue to diagnose, because of course a suspended app will not log anything. I scoured the internet for a solution for weeks over the last couple of years. I tried the popular cordova-plugin-geolocation; I forked it and incorporated all the suggestions I could find for running in the background; I spent a considerable amount of time trying to get the commercial TransistorSoft plugin to work.

In the end, I had to conclude that it's simply not possible on iOS. On Android, it is possible as long as you set up the wake locks properly.

To be fair, I'm not sure that even a native app on iOS could achieve this.

Recommendations

So, is it a good idea to use Cordova, or not?

While an additional layer of abstraction provides gains in the speed of development, I would suggest that a good rule of thumb is to assume that the risk due to exposure to bugs, limitations and complexity rises with the square of the number of layers involved. So with Cordova, it's 4 times as much as when writing native applications. With something like Ionic, it's 9 times as much (native + Cordova + Ionic).

My recommendation is to use Cordova only for simple apps that don't do much with the sensors and do nothing or very little in the background. If you need to create something fairly complex, or something with native look and feel, then you're better off biting the bullet and developing native applications.

I also recommend against going any higher than two layers of frameworks, as it's simply too brittle and bug-prone, so something like Ionic is completely off limits for me.