Case study

F4goals: one codebase, web and Android, offline-first

F4goals is a task manager where you slide a task to set its progress from 0 to 100 percent and nest subtasks to any depth. It ships to f4goals.com and Google Play from one Expo and React Native codebase, with an offline-first sync layer and native Android home-screen widgets.

1 codebase
web at f4goals.com and Android on Google Play
200+
automated tests, with TypeScript and lint errors held at zero
2
native Android home-screen widgets written in Kotlin
Retry queue
failed syncs are kept and retried instead of dropped

The problem

Most to-do apps know two states, done and not done. Work that spans several days therefore sits unticked until the moment it is finished, and a project looks stalled when it is half complete. The brief was an app where partial progress is a first-class value, so forward motion is visible, and where subtasks nest as deep as the work does.

It also had to work on a train: the target user is one person managing their own work, often on a phone with intermittent signal, so the app could not depend on a live connection to feel fast or to avoid losing changes.

What we built

One Expo codebase for web and Android

The app is built once with Expo Router and React Native and exported as a static site for the web while building a native Android app from the same code. Per-route metadata, a sitemap, robots rules and FAQ structured data are generated from one config, which is how the web version is discoverable in search without a separate marketing site.

Offline-first sync with a durable outbox

All state lives locally first, using Zustand with persistence, so every interaction is instant. Writes are queued in a durable outbox and flushed to Supabase when the device is online. A rejected write moves to a persisted dead-letter list and is retried when the connection returns, rather than being discarded, so a failed sync becomes a delayed edit instead of a lost one.

  • Optimistic creates with temporary ids that are swapped for real rows once the server confirms.
  • Progress writes debounced so dragging a slider does not flood the network.
  • Row-level security in Postgres so users only ever reach their own data.

The interaction that defines the product

Setting progress is a horizontal drag on the task row, with drag-and-drop for reordering and re-parenting across projects, on both web and native. Getting that gesture to feel right on touch and mouse took more iteration than any backend piece, and it is where the product is differentiated.

Native Android widgets and release engineering

Two Android home-screen widgets, Up Next and Project, are written natively in Kotlin and fed from a JSON snapshot the app writes to shared storage, wired in through an Expo config plugin so they survive rebuilds. Releases go through EAS builds to Google Play, with Sentry capturing errors and session replay.

Outcome

A product live on both the web and Google Play, maintained by one developer, with more than 200 automated tests and a clean type-check. Edits made offline sync without loss, and the Android widgets show the next tasks without opening the app.

It is a young app with a small user base and a 5.0 rating from its first users, so we do not quote install or growth numbers. What the project demonstrates is the engineering you would get on yours: a single codebase across platforms, an offline-first data layer, native modules where React Native alone is not enough, and a release pipeline to the store.

Known limits

F4goals is deliberately single-user, with no teams or sharing, and Android plus web only for now, with no iOS build yet. Those are product decisions, not oversights, and they kept the scope small enough for one developer to ship and support well.

Stack

  • React Native
  • Expo Router
  • TypeScript
  • Zustand
  • Supabase
  • Kotlin
  • Jest
  • Sentry

Have a project like this?

Tell us what you're building and you'll get a clear scope, timeline and fixed quote — at no cost.

Get a Free Quote