DEV Community

Dayana Jabif
Dayana Jabif

Posted on

Sometimes the best migration is the one where you don't move any data

I'm modernizing a Capacitor app that had been running on the same storage stack for years: @ionic/storage + localforage-cordovasqlitedriver + cordova-sqlite-storage.

Three packages, one of them untouched since 2022, just to persist a handful of JSON strings and a queue of pending sync payloads.

It wasn't a "nice to have" refactor. The stack was actively blocking native store releases:

๐Ÿšซ Google Play's 16 KB page size requirement. The bundled SQLite .so files were 4 KB-aligned. For an app targeting Android 15+, that's a rejected upload, not a warning.

โš ๏ธ An unmaintained bridge. The localforage driver in the middle hadn't shipped a release since June 2022.

๐Ÿ“ฆ SQLCipher you never asked for. With these packages you ship an encryption library, and inherit the export App Store compliance paperwork that comes with it, whether or not you ever encrypt a byte.

I evaluated three paths:

  1. A key/value store like Capacitor Preferences was out. The app queues updates every 30 seconds, so offline data piles up fast, that's queue-shaped data, not preferences.

  2. The community SQLite plugin was the obvious free option, but it imposes its own filename convention (SQLite.db) and its own directory, so the existing database can't simply be opened. It has to be imported. Its migration helper also silently mangles extension-less filenames, which is exactly what localforage created. On top of that, it uses SQLCipher so it also forces you to declare an encryption export regulation on iOS whether you need it or not.

  3. The Capawesome SQLite plugin ended up being structurally different:

โœ… Arbitrary file paths. It opens the existing database where it already lives. No copy, no rename, no import step. Zero data migration for a fleet of devices in the field, and a rollback to the previous app version still sees its data, because there's only ever one file.

โœ… System SQLite on Android. It builds on androidx.sqlite instead of shipping its own binary. No native library, no 16 KB problem to solve.

โœ… Encryption is opt-in. SQLCipher sits behind a Gradle flag. Don't need it, it isn't in your APK.

The measured result after the swap: libsqlcipher.so gone from the APK, debug build down from 99 MB to 78 MB, no migration code to write, test, or support, and the storage layer stopped being the thing blocking Play submissions.

When "migrating" means changing one import and deleting three dependencies, that's the kind of upgrade that actually ships.

Anyone else fighting the 16 KB deadline?

Top comments (0)