I built Mindleau so someone can start journaling without an account or internet connection: each entry is saved to the device first, and optional cloud sync runs separately in the background. In this implementation, Flutter provides the iOS and Android app, Drift and SQLite hold the local source of truth, and Firebase Auth and Cloud Firestore support sync when available. This is one app’s architecture and set of tradeoffs, not a universal recipe or a guarantee of security.
Why local-first shaped the architecture
Mindleau is a free iOS and Android brain-dump journal and mood tracker. I wanted its basic use to work before sign-up and without connectivity, rather than making a cloud account the gate to writing. That makes the local database more than a cache: the app can read and save entries there whether or not sync is configured or reachable.
As an Amazon Associate I earn from qualifying purchases.
The stack is Flutter, Drift’s typed SQLite interface, Firebase Authentication, and Cloud Firestore. Provider with ChangeNotifier and ValueNotifier handles app state. These choices describe this implementation; the article does not report package versions, benchmarks, or an independent security assessment.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteKeep the UI away from Firestore
The central rule in my design is: “The UI never talks to Firestore.” The UI reads and writes through a repository. That repository persists data in SQLite, while a separate sync service handles communication with Firestore. This boundary keeps a network failure from becoming a prerequisite for saving an entry.
#1 Best Overall
When a user saves an entry, the app inserts it locally, marks it as pending, and starts a sync attempt without making the UI wait for that attempt. The entry is immediately available from SQLite; if sync fails, the local copy remains and the pending work can be retried when connectivity returns.
Represent pending edits and deletions
For syncable rows, I store a remote document ID, an updated timestamp, a deleted timestamp, sync status, and last-sync timestamp. Together, these fields let the app associate a local row with its remote document, identify work that still needs to be sent, and record when a row was last synchronized.
Deletion needs explicit representation. Rather than removing a synced row outright, the app records a deletion timestamp—a tombstone—so that deletion intent can travel to another device. Without that signal, a later pull could make an item reappear because the remote copy would have no indication it was deleted locally.
Rank #2
This implementation keeps sync metadata on each row. Another design is a separate outbox table that records operations to deliver. The article describes the per-row approach but does not compare the two with measurements; the relevant choice depends on how much operation history and retry control the app needs.
Pull first, then push local work
The sync service follows a simple sequence: pull remote changes, then push local preferences and pending records. For records that changed on both devices, it uses updatedAt as a last-write-wins rule. The version with the later timestamp takes precedence.
That rule is simple, but it can discard an edit: if the same entry is changed offline on two devices, one version will win instead of merging both changes. I considered that tradeoff acceptable for this personal-use app. It is my judgment about this product, not a generally safe conflict policy. An approach that preserves or merges concurrent edits, such as a CRDT, can avoid that specific loss mode but adds complexity.
Let people begin without an account
Local journaling does not require Firebase authentication. When sync begins, the app signs in anonymously and uses that identity for cloud sync. The author’s account path allows an email-link credential to be linked later to the same UID, so account-free onboarding can coexist with a stable cloud identity.
If anonymous authentication is disabled or unreachable, the app skips sync and continues using local SQLite. This keeps the account and network dependency on the sync path rather than the writing path.
Bound network waits and keep failures off the save path
Network calls use a 15-second timeout in this implementation to avoid waiting indefinitely on an unreliable connection. Sync errors are logged. Because the UI does not await sync, those errors do not undo a local save; pending work can be retried when connectivity returns. The timeout is the author’s implementation detail, not a measured guarantee of reliability.
Rank #4
Plan Drift migrations for existing installs
Adding sync to a shipped local database means changing a schema that already contains user data. I versioned the Drift schema and added upgrade steps for sync columns and a table. One migration lesson was that a new NOT NULL column added to an existing SQLite table needs a database-level default if old rows must satisfy the constraint.
Drift’s Dart-side clientDefault is not the same as a SQL default: it supplies a value in Dart and does not provide the database-side default required by a raw ALTER TABLE migration. When evolving a local schema, account for how existing rows are populated, not only how newly inserted rows receive values.
Sync only data that earns its way into the cloud
I keep guided stillness sessions on the device. They contribute to streaks and a 30-day heatmap, but I judged their cross-device value too small to justify additional sync complexity and sending that data off-device. That is a product decision rather than a rule that this kind of data should always remain local.
Best Value
Selective sync makes the tradeoff explicit: syncing a table can improve continuity across devices, while local-only storage avoids that transfer and its associated implementation work. Decide data by data whether cross-device access is worth the cost.
What this design does—and does not—establish
The implementation demonstrates one way to make local saves independent of cloud availability: SQLite is the source of truth, the repository separates UI from storage and sync, and background synchronization carries pending changes when possible. It also has clear limits: timestamp-based conflict resolution can lose simultaneous offline edits, and successful account-free use does not itself establish anything about encryption, privacy protections, or security quality.
The account is Ayesha Iftikhar’s first-person description of Mindleau, published on DEV Community on September 23 and edited September 26; the year is inferred from the source’s publication label as 2025. Read the original article.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




