r/FlutterDev • u/Low-Blueberry-6001 • 6h ago
Tooling Ephemeral isolates + Argon2id with dynamic memLimit to avoid killing 2GB RAM devices - VeneFinanzas
I've been posting progress updates on this other venezuelan sub, but this time I'm bringing something more technical
CONTEXT:
VeneFinanzas is a personal finance management app I've been building solo, with Flutter, that calculates how devaluation and inflation affect each user specifically.
It natively handles COP, EUR, USDT, USD and VES, and is primarily designed for Venezuela. Everything runs locally, no account, no server, and backups are encrypted so the user can back them up wherever they want
That's where I had to think through the edge case of users with 2GB of RAM. Even though they're less than 10% of users, I didn't want to cause them problems using the app. Encrypting those backups with Argon2id, with a fixed 64MB memLimit regardless of device, is risky, especially with other apps running in the background
---
I ended up using two profiles:
kMemProfileBajo, 32MB
kMemProfileAlto, 64MB
With 3584MB of physical RAM as the threshold.
The profile byte is stored inside the encrypted file itself, so decryption always uses the memLimit it was encrypted with, regardless of the current device
My initial plan was to use the standard interactive (64MB) + moderate (256MB) profiles, but the 256MB one would end up cannibalizing RAM on the more limited devices.
I landed on 32/64 because a user normally restores their own backup on their own phone, so a backup encrypted at 64MB will almost never end up being decrypted by someone with 2GB.
However, since the memProfile is fixed in the file from the moment of encryption, it still has to remain decryptable if that scenario does happen. That's why I capped it at 64 and no higher, I ran the numbers, and with the ~300MB the app already uses by default, adding the memLimit on top pushes it close to a Low Memory Kill, which is exactly what we're trying to avoid.
Keeping 64MB as the upper bound is the max stable value before entering dangerous territory, and for regular usage, lower-RAM users simply use 32MB without any hassle
Everything runs in an ephemeral isolate via Isolate.run(), spawned just for that job and torn down once it's done. Derivation never blocks the main isolate, and the heap (including the 32/64MB Argon2id uses) gets freed the moment the isolate dies, not whenever the GC gets around to it on the main isolate
Also: sodium_libs_sumo needs a BackgroundIsolateBinaryMessenger, and Isolate.run() doesn't register one by default. You have to grab RootIsolateToken.instance from the main isolate before spawning (it's only valid there), pass it in, and call BackgroundIsolateBinaryMessenger.ensureInitialized(token) as the first line inside the child isolate.
---
If anyone's run into something similar with Argon2id/isolates in Flutter, has feedback on the approach, or just wants to check out VeneFinanzas, I'm around