← All case studies
Professional work · mobile

Fieldbook

A field app for construction crews that keeps working where there's no signal.

Note
Name and branding changed
Stack
React Native · TypeScript · WatermelonDB · Redux Toolkit
Status
In production on iOS and Android
01

The problem

On a job site, connectivity comes and goes. Crews still need the latest plans, to-dos and forms, and still take photos and fill in reports in basements and on rooftops.

Every change they make has to reach the office eventually, in the right order, without being lost or applied twice.

02

What the app does

Everything works offlinePlans with annotations, to-dos, forms with QR scanning, daily reports, timesheets, work orders and photos, all usable with no connection.
Sync per moduleEach API domain pulls on its own and tracks its own last-pulled time per project, through one shared helper for batching, logging and permissions.
Changes as a queueEvery offline write is stored as an event and pushed in order. A built-in inspector lets users and support see, retry or clear anything stuck.
A codebase that scalesFeature modules with strict layers, generators that scaffold a feature, use case, component or screen, and two Jest setups for device and Node tests.
03

How it works

Each feature keeps business rules apart from storage and screens. Reads come from the local database, always; the network only ever feeds that database or drains the queue of local changes.

Inside a feature module

Screens & hookspresentation
  • Reactive queries via RxJS
  • Resolve dependencies
Use casesdomain
  • Business rules only
  • No framework imports
Repositoriesdata · tsyringe
  • Implement domain interfaces
WatermelonDBSQLite
  • The source of truth on the device

Sync

Offline event queuelocal writes
  • Ordered intents
  • Retry or heal from the inspector
APIREST
  • Applies changes
  • Returns active IDs per module
Pull per modulebatch write
  • Create and update in one batch
  • Delete what the server no longer lists
04

Key decisions

  1. Strict layers, enforced by the linterEach feature has domain, data and presentation layers, and ESLint blocks any import past its public barrel. The rule holds even under deadline pressure.
  2. The server lists what exists; the device deletes the restEach pull returns every active ID, and the client removes local rows that are missing. No tombstones or deletion log needed on the server.
  3. Writes are eventsOffline changes are stored as ordered intents, not as edited rows waiting to be diffed. That makes them visible, retryable and easy to reason about.
  4. Dependency injection for fast testsUse cases and repositories are registered with tsyringe, so business rules run in plain Node tests without a device or simulator.
  5. Respect the user's data planA Wi-Fi-only setting is checked before any module syncs, along with connectivity and session guards.
05

Where it stands

Today

In production on the App Store and Google Play, released through code review, preprod and QA on deployed builds.

About this case study

The product name, company, identifiers and endpoints have been changed. An anonymized code sample is available on request.

React NativeTypeScriptWatermelonDBSQLiteRedux ToolkitRxJStsyringeJestDetoxMaestroSentryDatadogi18n FR/EN

Have something people will use every day?

I'm happy to walk through any of these projects, including the private ones, in more depth.