Type to search pages and rules.

Browse pages

Mobile Data and State

Data handling in a mobile app routes every use case through an Application handler, the one place where that use case is defined. Every piece of state carries a declared classification deciding whether it is persisted, kept on the device, or recomputed. This page states the mobile store, storage, and client those rules land in, following Frontend Data and State (litespec/frontend/data#route-use-cases-through-application, litespec/frontend/data#classify-state, litespec/frontend/data#update-the-cache-a-command-changed), and a team that applies it while writing handlers and screens keeps one definition of each use case and one source of each value.

Markdown6 provisions

Stack: any Layer: frontend-data

Concepts#

The Three State Classifications#

Durable state is what the user made: persisted, versioned, and exported. The expense report the visitor files is durable state, because the visitor built it and it must survive a restart and an export. Device-local state is persisted but stays on the device, excluded from export. Derived state is computed from durable state and never persisted: an expense total is derived from the report lines each time it is read.

Standards#

Load Persisted State Once#

ruleruleguidance
litespec/frontend/mobile/data#load-persisted-state-once
Statement

Persisted state MUST load once at startup into a typed store that screens read from, with screens showing a loading state until it is ready.

Why

Storage reads are slow and fallible, so the product reads once at startup into a typed store that screens read from. A screen that reads storage on render pays an input-output call per frame and can render half-loaded state. A screen renders its loading state until the store finishes loading, so it never shows an unloaded store as ready.

Checka reviewer answers

Reviewed forWhich persisted state loads outside startup, or renders a screen before the typed store is ready? Look in the startup sequence and the screen loading states.

Marked guidance: a reviewer answers this question.

Keep Persisted State in App-Private Storage#

ruleruleguidance
litespec/frontend/mobile/data#keep-persisted-state-in-app-private-storage
Statement

Persisted state and media the user adds MUST live in the app's private storage, written atomically.

Why

App-private storage keeps the user's data inside the app's own sandbox, away from other apps and from the shared media library. A crash mid-write leaves a half-written file, so each write lands in a temporary file that then replaces the target, keeping the previous complete file until the new one is whole. Media the user adds travels with the state that references it, so an export or a reinstall keeps both together.

Example

The filed expense report persists as versioned durable state in app-private storage, with the photographed receipts beside it in the same private files.

Checka reviewer answers

Reviewed forWhich persisted state or user-added media lives outside app-private storage, or writes without a temporary file? Look in the storage writes.

Marked guidance: a reviewer answers this question.

Copy Picked Media into the App#

ruleruleguidance
litespec/frontend/mobile/data#copy-picked-media-into-the-app
Statement

Media picked from the device MUST be copied into app-private storage on import rather than kept as a reference to the picker's URI.

Why

A picker's URI is a loan, not a gift: the platform can revoke it when the picker closes, and the source file can move or vanish. A screen holding only the URI shows a broken image after either happens. Copying the media into app-private storage on import keeps the app's copy under the app's own control.

Checka reviewer answers

Reviewed forWhich imported media keeps a stored picker URI instead of a copy in app-private storage? Look in the media imports.

Marked guidance: a reviewer answers this question.

Make Refusals Explicit#

ruleruleguidance
litespec/frontend/mobile/data#make-refusals-explicit
Statement

A handler MUST return a typed result union covering success and each named refusal, with each screen mapping every refusal to a message.

Why

Signaling a refusal through null, an empty collection, or a bare boolean tells the caller that something was refused but not what. Every caller then guesses, and two callers guess differently. A typed result union names success and each refusal, so each screen maps every refusal to the message the application holds for it. This realizes litespec/frontend/data#map-every-failure-to-one-problem-type.

Checka reviewer answers

Reviewed forWhich handler unions miss success or a named refusal, and which screens leave a refusal unmapped to a message? Look in the handler result types and the screens.

Marked guidance: a reviewer answers this question.

Call the Server Only Through the Generated Client#

ruleruleguidance
litespec/frontend/mobile/data#call-the-server-only-through-the-generated-client
Statement

A server consumer's app MUST call the backend only through the generated client in {ProjectName}.Client.

Why

The generated client owns the base address, the credentials, the headers, and the mapping from a failure to a result. A call that builds its own request opts out of all four at once: it can point at the wrong host, miss the session, and surface a raw failure outside the handler's result shape. Routing every backend call through the one client keeps those decisions in one place. This realizes litespec/frontend/data#use-one-configured-client-per-application.

Checka reviewer answers

Reviewed forWhich backend calls in a server app bypass the generated client in {ProjectName}.Client? Look in the backend call sites.

Marked guidance: a reviewer answers this question.

Conventions#

Refuse Commands While Offline#

conventiondefaultguidance
litespec/frontend/mobile/data#refuse-commands-while-offline
Default

While the network is unavailable, a screen shows its failure state and queues no command; the visitor retries the command in the foreground when the network returns.

ReplacementA project records a departure as an explicit local convention naming each command it queues offline, its stored form, and its replay order.

Why

A queued command runs later against state the visitor cannot see, so an expense submitted on a train can land after its trip closed or its approval policy changed. Showing the failure state keeps the refusal where the visitor caused it, and queuing nothing keeps every command tied to the screen that sent it. A project that needs offline capture states that departure and its replay rules instead of letting each screen improvise a queue.

Example

Filing expense EXP-1042 for the Rotterdam client visit with no network shows the failure state on the expense screen and sends nothing; the visitor submits the expense again when the network returns.

Checka reviewer answers

Reviewed forWhich screens queue a command while offline without a recorded departure naming the queued commands and their replay order, and which offline screens show no failure state? Look in the command call sites and the offline screens.

Marked guidance: a reviewer answers this question.