Built for a small-business finance startup. Client named on request, under NDA.
A finance app for people who keep supplier contracts and unpaid invoice values on their phone. We inherited the codebase in React Native and continued in React Native rather than rewriting it in our own stack — then moved the records behind the Android Keystore, where a lost phone stops being a data breach.
A small-business finance startup was building a tool for owners and freelancers who need to track invoices and bills without running an accounting department. The intended flow was simple to describe: photograph a document, have it categorised automatically, and browse everything organised by client or by vendor.
The part that shaped every technical decision came at the end of the brief. The records had to stay protected if the phone were lost or stolen. Not “we should look at security before launch” — local-first storage and PIN-based encryption were stated as requirements up front, and they determined the architecture.
Two things made this harder than the feature list suggests.
The first was the codebase. It already existed, and it was React Native. Most of our work is Flutter, and the honest options were to continue in a stack that isn’t our default, or to propose a rewrite that would have spent the client’s budget re-reaching the position they already occupied.
The second was that finance apps routinely get local storage wrong, and the failure is invisible until it matters. Records get written to ordinary local storage because it is faster to ship and because nothing looks broken afterwards. The consequence only shows up later: a device backup extraction, a forensic tool, or a rooted phone reads the whole file in plaintext. For a user whose device holds supplier contracts, outstanding invoice values and a client payment history, that is not an abstract risk — it is a set of records with direct commercial value to whoever ends up holding the handset.
A Flutter shop takes a React Native project and rebuilds it in Flutter, because that is the stack the team is fastest and most confident in.
Why it fails hereThe rewrite is billed to the client, and at the end of it they own the same product they started with — minus the budget and the time, plus whatever regressions a full port introduces. Our preference is not a reason for them to pay twice.
What we didWe assessed the project, accepted it on its own terms, and delivered in the stack it was written in — including the security work, which was the substantive part of the engagement.
What it boughtThe client’s existing investment kept its value, and the money went into the encrypted vault and the capture flow instead of into reproducing work that already existed. It is also the honest test of whether “partner-level engagement” means anything: it is easy to say when the project happens to match your preferences.
Gate the app behind a PIN screen and store the records normally underneath. The user experiences it as protection, and in the ordinary case it is.
Why it fails hereA PIN that only controls navigation is defeated by anything that reads the filesystem without going through the app — which is precisely the scenario the requirement exists to cover. The threat model is a phone in someone else’s hands, and a phone in someone else’s hands does not have to open your app.
What we didSensitive records and credentials go through react-native-keychain, which uses the OS-level secure store — Android Keystore. MMKV handles fast local state behind its own encryption layer, and AsyncStorage carries only the genuinely low-sensitivity persistence. The PIN unlocks the vault rather than the screen.
Losing the device stops being a data breach. The split also makes the sensitivity of each piece of state an explicit decision at the point of writing it, rather than something inferred later from where it happened to end up.
Invoice and bill capture with image processing, tagged by client, vendor, type and date. Validation runs at the point of entry, so malformed records are rejected before they reach storage rather than cleaned up afterwards.
All financial records stored locally and encrypted, with the PIN gating both app open and the sensitive screens within it. Nothing leaves the device unless the user explicitly exports it.
An Axios-based client for the server-sync features, cleanly separated from vault data. The vault works whether or not the network does, and a change to the API surface cannot reach the stored records.
Filter by client, by vendor, by date range or by status — the questions a small-business owner actually asks at the end of a month, answerable without an export.
The app shipped on Google Play as a production application. The encrypted local vault gives small-business users real protection for financial records without requiring cloud sync, or a backend to be trusted with them. The React Native codebase was handed back clean — validation throughout, a well-typed network layer, and a storage split that documents its own intent.
Build facts, verified against the delivered app. We don’t publish outcome numbers we didn’t measure.
Some of this engagement stays private. Naming what’s missing seems fairer than quietly leaving gaps.
If you have an existing React Native app that needs a feature, a fix or a full handoff, we can take it. We are not a Flutter-only shop that declines everything else — we assess the project, meet the codebase where it is, and deliver in it. You should be sceptical of a partner whose recommended first step is always a rewrite into their preferred stack.
And if you’re building anything that holds financial or personal records on a device, decide where the encryption boundary sits before you build the screens. Retrofitting it means migrating live user data, and by then the shape of the app is arguing against you.
We’ll read it and tell you honestly whether it needs a rewrite or a steady hand. Most of the time it’s the second one.
More proof: all case studies · the engineering blog · what clients say