Stokky is a Flutter app, written in Dart, with Bloc for state management. This page covers the technical choices holding it together: local storage as the source of truth, a sync layer that resolves conflicts instead of overwriting them, and fixes shipped without going through an app store.
Local storage isn't a cache — it is the source of truth. The app opens with no account and no network, and every read and every write goes through an SQL database embedded on the device. The UI never waits on a round trip; the network, when there is one, only catches up with what was already decided locally.
Starts instantly, no sign-up
Writes commit locally, then travel
Local-only mode stays usable indefinitely
Synchronisation
Every change enters a durable queue that survives the app being closed. Pulling is incremental: a cursor remembers where the last pass stopped, so only rows that actually changed travel. Failures retry on a growing delay, and the state stays visible — a pending-change counter, a banner when sync is blocked. Photos take their own path, separate from text, because they share neither its size nor its urgency.
Queue persisted, replayed on restart
Cursor-based incremental pull, never a full reload
Backed-off retries, no silent loss
Photos sync on a path of their own
Conflict handling
A write never blindly replaces a row. It carries the version it believes it is amending; if the row moved elsewhere in the meantime, the server refuses, the app captures both states and files a conflict. A resolution screen shows the difference field by field — mine, remote — separating what changed from what didn't. Where no merge would mean anything, such as the zones drawn on a photo of a piece of furniture, the last write simply wins: asking would have cost the user more than it gave them.
Conditional write: a per-row version check
The conflict is set aside; the app keeps working
Field-level resolution, an explicit choice
Last write wins where arbitration would be absurd
The rest of the machinery
What makes the first three sustainable.
Realtime
When several people share a household, changes arrive without a refresh. A realtime subscription wakes the sync layer as soon as a row moves on the server, instead of polling in a loop.
Over-the-air updates
A fix reaches already-installed devices without going through app store review — hours instead of days. Every patch is validated on a pre-production channel before it ships.
Architecture
Feature-first MVVM, with abstract repositories as the only seam. Dependencies point one way: view → cubit → repository. That seam is what lets the web build run without a local database, and every cubit test run without one at all.
Predictable state
State lives in Cubits, as immutable state classes rather than loading booleans scattered about. Anything that wouldn't survive a screen rebuild stays a plain setState: the boundary is written down somewhere, not guessed at each screen.
Tests on four layers
Cubit, repository, widget, integration. The cycle is red-green-refactor: the test fails first, or it proves nothing. The phone screenshots on the landing page are produced by those same tests — they cannot lie about the app.
Additive schema
A migration adds a column; it never renames or drops one. An older build therefore keeps working against a newer server — a hard requirement once you accept that not everyone updates on the same day.
Design system
Colours, spacing, radii and type are tokens, never values hand-written into a widget. A catalogue of shared components shows them in isolation, in light and dark. This page uses the same tokens as the app.
One codebase, six targets
Android, iOS, the web and the three desktops all come from the same code. The home-screen widget is app UI rasterised to an image, so changing its layout stays Dart, not native code written twice.
Three languages, none hardcoded
French, English and Malagasy, without a single string hardcoded into a widget. The framework ships no Malagasy for its own built-in labels, so those fall back to French rather than English — the useful fallback here.
Shipping
Three environments install side by side on the same phone, each with its own local database, so a build can be tested without touching real data. Nothing reaches production without going through staging first.
0Platforms
0Languages
0Test layers
0Trackers
None of this is visible. That is the point.
You open the app, add an item, put the phone away. The rest is our problem.