Quality issues in Ionic/Cordova/Capacitor plugins

I like Ionic. I have delivered a number of apps now using Ionic, and in my opinion it’s the best cross-platform mobile framework around. I’ve also tried NativeScript and Flutter and they don’t compare. Ionic is the best of the bunch. Probably because it’s architecturally very simple and relies only on the device’s support of HTML and JavaScript, which at this point is very mature. Other frameworks employ more complex techniques, which tend to work well for the simple stuff but less well when the abstraction starts leaking.

But Ionic is nowhere near perfect.

In cross platform app development, the biggest pain points are always in the parts of your app that need to touch native functionality. File access, databases, location, sound, etc, these are things where you need to break out of whatever nice abstraction the framework has crafted for you and venture into another realm. In Cordova (and Ionic’s Capacitor), that realm is a JavaScript API injected into the webview which invokes native code compiled into the app via a plugin system.

Plugin ecosystems are always a bit hairy because you’re at the mercy of whatever “the community” has made available. Maybe there’s a plugin that fits your needs and is well maintained and works well, but more likely there’s one that kind of works that is no longer actively maintained but somehow still has enough staying power that nobody has replaced it with something better.

My approach to working with Cordova/Capacitor based projects has changed a lot over the years I’ve been using it. Initially, I treated it as an HTML/JavaScript front-end web project and expected to only maintain code within the web side. Now, I am very quick to write my own native code when the plugins aren’t up to standard.

Apache provides some official Cordova plugins for common things, but the quality of even these is dubious.

Files

The Cordova file plugin’s iOS implementation puts its write operations into background threads, without any concurrency control. This means that in the event that there are two writes to the same file happening in very short succession, we can end up with bytes from both writes being merged together in the file. This is fairly unlikely to happen, so it’s the kind of thing that gets missed by testing and you only find out about it when you have tens of thousands of real users and every so often your app corrupts someone’s data.

A workaround is to make sure that file writes are queued in your app code so that each file can only have one write happening at once, but this just shouldn’t be a problem.

Media

Another example is the official Cordova media plugin. The media plugin provides functionality for audio playing and recording.

In recent Android releases, Android has tightened permissions on filesystem access. As of API version 29 (Android 10), apps can’t write to arbitrary locations on the SD card. They may only write to their own space. In API v29, you can request legacy access using android:requestLegacyExternalStorage="true" in the manifest, but this is not available for API v30 (Android 11) and above. Apps targeting Android 11 need to be compliant.

Unfortunately, the media plugin hard codes access to the SD card when recording files. In other words, Android 11 was released in September 2020, and as of May 2021, the Cordova media plugin is still incompatible with Android 11.

This isn’t the only problem the media plugin has. When starting audio recording, an important thing to consider is that unless you specify otherwise, the device’s power saving system is going to kick in at some point and send the device to sleep. This is probably not what you want. Usually you’d use a thing called a wakelock, but the plugin doesn’t do this so you need to rely on other plugins to access a wakelocks.

There are a couple of plugins around: Insomnia and PowerManagement. However, as you can see from the GitHub repositories, neither plugin has been updated for a long time. In Android, the concept of a wakelock has undergone development during this period. For recording audio, what you really want is a partial wakelock, i.e. a wakelock that prevents the device from sleeping but allows the screen to go off. The PowerManagement plugin looks like it supports this, but in reality uses SCREEN_DIM_WAKE_LOCK which was deprecated in APIv17 (Android 4.2) and replaced with PARTIAL_WAKE_LOCK, which is what you want.

At some point, it becomes easier to just write your own plugin and write actual native code than to try to work around all these limitations.

Enter Ionic, and Capacitor

Ionic is trying to replace Cordova entirely with Capacitor. Ionic have written some blurb about Capacitor, but essentially, Capacitor is a toolkit and framework around a webview. Just like Cordova, but with more transparency over the native side of the project, especially during build time.

It’s still a work in progress so it remains to be seen how well it replaces Cordova in the long term.

Capacitor provides its own set of official plugins, including a file access plugin, but not a media plugin, so you can still expect to use the Apache Cordova infrastructure for some things.

Capacitor provides compatibility with existing Cordova plugins, and Ionic lists a lot of these under Ionic Native. Ionic Native sounds contradictory, but it’s actually just their take on Cordova’s plugin system, with added TypeScript API definitions. It’s an index of plugins, basically.

Unfortunately, it doesn’t seem to be very well curated, so there’s no guarantee that anything you see listed on Ionic Native is actually going to work.

For example, the Native Audio plugin is listed with commands to install it for Capacitor. But it doesn’t work with Capacitor, because it has a hard coded path to the www/ directory, which doesn’t exist. In Capacitor, this directory is called public/.

In summary

My experience with Ionic in 2021 is that the core framework is great, but the plugin ecosystem is not. Ionic as a whole is a very solid way to build user interfaces on mobile, but as soon as you need to touch native functionality things can get a lot more complex than they really should. Don’t expect plugins to solve your requirements, and do expect to get your hands dirty with native code.