A backend outage should change what your mobile app can do, without erasing the work a user has already done.
For Product Owners and Technical Directors, mobile resilience is a product decision as much as an infrastructure decision. A technician may need to finish an inspection while the API is unavailable. A warehouse supervisor may need access to an already downloaded work order. Neither task should depend on every cloud service being healthy at that moment. This article explains how offline-first data, deliberate Flutter state management and robust React Native error handling can preserve useful workflows during an AWS or GCP outage. It also explains the limits: local work needs durable storage, honest status messages and a safe reconciliation process before it becomes confirmed business data.
Contents
- Define the service that survives an outage
- Cache a working dataset, not just the last screen
- Model independent states in Flutter
- Handle failures explicitly in React Native
- Queue actions durably before showing success
- Synchronise safely when the backend returns
- Respect device lifecycle and local security
- Make degraded operation understandable
- Modernise one workflow before the whole app
- Common mistakes
- FAQ
- Mobile architecture modernisation
Define the service that survives an outage
Choose which workflows remain available with local data, which accept provisional actions, and which require a live server decision.
An internet connection does not prove that your API, authentication provider or regional database is available. A phone can have a strong Wi-Fi signal while a backend returns errors or never responds. Design for service failure as well as loss of connectivity.
Graceful degradation means reducing capability in a controlled way. It does not mean claiming every feature works offline. Android’s architecture guidance distinguishes online-only operations from locally persisted and queued writes, depending on the workflow’s requirements. Android offline-first guidance
An illustrative field-service app might use this contract:
| Workflow | During an outage | After recovery |
|---|---|---|
| View downloaded job instructions | Available with a freshness label | Refresh from the server |
| Record notes and inspection answers | Saved locally with a pending status | Submit and reconcile against the current job |
| Capture a photo | Stored locally, subject to available device space | Upload, verify and attach to the correct record |
| Change a job assignment | Save a proposed change only if the product allows it | Server validates ownership and permissions |
| Confirm payment or reserve scarce stock | Require an authoritative online decision | Complete only after server confirmation |
This is a proposed product contract, not a promise that every field-service workflow is safe to perform offline. Set the rules with the people responsible for operational accuracy.
Cache a working dataset, not just the last screen
Store the information needed to finish an approved workflow, together with its identity, version and freshness.
A useful work-order cache might include instructions, reference forms, relevant asset details and required attachments. Scope it to the authenticated user and organisation. Download it before work starts, with a clear indication of what is actually available on the device.
Use a persistent local database for structured records and a managed file store for attachments. An in-memory state container cannot preserve work after the app process is terminated. Decide how much data to retain, when to expire it and what to do when storage fills up.
Flutter’s offline-first guidance describes repositories combining local and remote data. One approach emits local data first, then refreshed remote data; another reads from local storage and updates it through synchronisation. The right choice depends on freshness requirements. Flutter offline-first architecture
Separate a last-confirmed server snapshot from pending local changes. A background refresh must not overwrite a user’s unsynchronised edit. Store the server version used when the edit began so reconciliation can detect that the underlying record has changed.
For operationally sensitive data, an old value may be worse than an unavailable one. Give each field or workflow an explicit freshness policy. Cached instructions can remain useful while cached availability or permissions may need a stricter rule.
Model independent states in Flutter
Represent usable data, network activity and synchronisation status separately. A single loading / success / error flag is too coarse for an app that can keep working during a failure.
Flutter’s architecture guidance separates views, view models and repositories. Its recommendations include immutable data models. Apply that separation so widgets render meaningful state while repositories manage local persistence and remote access. Flutter architecture guide, architecture recommendations
A proposed screen-state model can contain:
| State field | What it tells the interface |
|---|---|
data | The currently usable local record |
lastConfirmedAt | When this record was last confirmed by the backend |
isRefreshing | Whether a remote refresh is in progress |
pendingActionCount | How much local work still needs acknowledgement |
syncStatus | Pending, sending, confirmed, blocked or conflicted |
serviceIssue | A classified failure relevant to this workflow |
When a refresh fails, retain the usable data and display a service-status message. When a local save succeeds, update the screen and show pending synchronisation. When the server rejects an action, preserve the draft and offer an appropriate next step.
Whether your existing Flutter app uses a view model or another established state pattern, keep persistence and synchronisation outside widget callbacks. A state-management library alone does not supply durable storage, conflict resolution or safe retries.
Handle failures explicitly in React Native
Classify a failed request before deciding whether to retry, preserve a draft or ask the user to act.
React Native provides the Fetch API and documents catching networking failures. HTTP errors also need explicit handling: Fetch can resolve with a response whose status indicates failure, so a successful promise is not sufficient evidence of success. React Native networking, Fetch response handling
A proposed error policy should distinguish:
- Network failure or timeout: retain the action and schedule a bounded retry when appropriate. A timeout can leave the server outcome unknown.
- Temporary service failure: preserve cached reads and eligible local writes; slow retries instead of repeatedly blocking the screen.
- Rate limiting: respect the API’s retry instructions and apply a retry budget.
- Authentication failure: pause submission and require a valid session. Keep the action associated with its original identity.
- Permission or validation failure: stop automatic retries and show what needs attention.
- Version conflict: retain both the local intent and relevant server state for reconciliation.
Give requests deadlines and cancellations appropriate to the workflow. On an ambiguous write timeout, retry through an idempotent server contract or check the action’s status. Do not interpret the timeout as proof that nothing happened.
Use React error boundaries to contain rendering failures, alongside explicit handling for request and event-handler errors. React documents that error boundaries do not catch ordinary asynchronous failures or event-handler errors. They cannot replace a networking policy. React error-boundary limitations
Queue actions durably before showing success
For an offline-capable write, commit the local change and its queued action together before telling the user it has been saved.
The proposed pattern is a transactional outbox on the device. One local database transaction updates the draft and inserts the pending command. If local persistence fails, report that failure rather than displaying a successful save.
A command should carry an action identifier, original user and organisation, target record, operation type, payload, base server version and retry state. Store dependencies for related operations. For example, a photo attachment should not be marked complete before its file upload and record association have been acknowledged.
The high-level flow is:
User action
-> local transaction: save draft + pending command
-> interface: saved on this device
-> synchroniser: submit with stable action identifier
-> server: validate, deduplicate and commit
-> local transaction: record acknowledgement + confirmed state
This is an architectural sketch, not a drop-in implementation. It depends on transaction support, protected storage and an API that implements the required validation and deduplication.
The same action identifier must survive every retry. The server should bind it to the user, operation and payload, retain the result for an appropriate replay window, and reject incompatible reuse. AWS explains how caller-provided identifiers help make retries safe when a response is lost after processing. AWS guidance on idempotent APIs
A locally persisted queue protects work across normal app restarts. It is not a backup against device loss, app deletion or damaged storage. Make those limits part of the product’s recovery plan.
Synchronise safely when the backend returns
Recovery is complete only when pending actions have a verified outcome and the interface reflects the resulting server state.
First establish a valid session and confirm that the relevant API can accept work. Treat connectivity changes as a trigger to attempt synchronisation, rather than proof that synchronisation will succeed.
Then process eligible commands in a controlled order. Preserve dependencies within a record, allow safe parallelism between independent records, and avoid letting one permanently invalid command block every other workflow.
Use bounded retries with exponential backoff and jitter. AWS documents that frequent retries can worsen a degraded service, and that retried operations need idempotent behaviour. Avoid a retry storm when many devices reconnect together. Retry with backoff
Conflict handling should follow business meaning. A server may reject an update made against an old record version. The app should preserve the user’s draft, fetch the current version and offer a merge or review path. Silently applying “last write wins” can discard another person’s work.
For an inspection record, separate append-only observations from fields that require exclusive ownership. For a stock reservation, an offline request may remain a proposal until the server confirms availability. These are design examples; the correct policy depends on your domain.
Pull subsequent server changes through a resumable cursor or another supported incremental mechanism. Protect pending edits from being overwritten and recover interrupted sync attempts after restart. Where an API responds with an asynchronous acceptance, retain a pending state until the business operation has actually completed.
This model applies whether the unavailable service runs on AWS, GCP or another provider. Switching endpoints cannot by itself reconcile divergent backend state. Coordinate client recovery with the backend’s authoritative data and failover strategy. Related reading: disaster recovery in the AI era.
Respect device lifecycle and local security
Schedule synchronisation around the operating system’s rules, and protect the data you now retain on the device.
React Native’s AppState reports foreground and background transitions. Use it to trigger a bounded recovery attempt when the app becomes active. It does not grant continuous background execution. React Native AppState
Apple lets apps request background processing, but the system decides when scheduled work runs. Build reliable foreground resumption and a visible manual sync option as well. Background execution should improve recovery without being its only path. Apple background-task guidance
Keep credentials in suitable platform-backed secure storage. React Native’s security documentation identifies Async Storage as an unencrypted key-value store and advises against using it for tokens or secrets. An operational database containing sensitive records needs its own protection and key-management design. React Native storage guidance
Define account-switch and sign-out behaviour before launch. Never submit one user’s queue under another user’s credentials. Explain whether pending work must be synchronised, retained securely for that identity or deliberately discarded. Apply expiry and permissions again when the server receives an action.
Offline access also needs a policy for expired authorisation. An unavailable identity service is not permission to bypass access controls. Decide what already-authorised local work may continue, for how long, and how exceptions will be handled.
Make degraded operation understandable
Keep the interface calm and specific: explain what is available, what has been saved and what still needs confirmation.
Useful messages include “Showing downloaded job details,” “Saved on this device. Waiting to sync,” and “This record changed elsewhere. Review before submitting.” Reserve “Synced” or “Confirmed” for an acknowledged outcome.
Provide a pending-work view with counts, the oldest waiting action and records needing attention. Users should be able to understand a blocked operation without reading a technical error log.
Disable only the features that require live authority. Keep navigation, cached records and allowed draft workflows available. An unavailable optional recommendation service should not blank the entire work-order screen.
Track queue age, retry attempts, conflicts, rejected actions and local persistence failures. These measures reveal whether apparent offline success is turning into a growing backlog after recovery.
Modernise one workflow before the whole app
Start with a commercially important workflow that users can safely complete with downloaded data and provisional writes.
Map its current failure path. Identify the data needed offline, the server-side decisions that remain mandatory, the storage and API changes required, and the visible states users need. Preserve the existing app’s proven workflows while introducing the repository, queue and reconciliation layers.
Validate the design with an outage exercise that includes a functioning internet connection but a failing API. Also test a response lost after server commit, process termination after local save, expired authentication, account switching, full device storage and simultaneous edits from two devices.
Measure whether the draft survives restart, whether replay creates duplicate effects, and whether pending work reaches a clear outcome. Test on the supported Android and iOS devices, not just in a simulator.
The effort depends on your API contracts, data sensitivity, attachment volume, existing persistence and conflict rules. Budget for backend changes, storage migrations, device testing, telemetry and ongoing maintenance. A generic timeline would hide those dependencies.
For related delivery considerations, see moving an AI prototype into production.
Common mistakes
- Keeping the queue only in memory or persisting it after showing a successful save.
- Treating network connectivity as proof that the backend is healthy.
- Retrying every failure, including rejected permissions and invalid data.
- Marking a request as confirmed before the business operation is acknowledged.
- Replaying commands without a stable identity or server-side duplicate protection.
- Assuming background synchronisation runs continuously on every phone.
- Overwriting local edits with a newly downloaded server snapshot.
FAQ
Can an app remain useful during an AWS or GCP regional outage?
Yes, for explicitly designed workflows whose required data is already on the device. It can display cached information and preserve eligible actions locally. Features requiring an unavailable server decision must remain pending or unavailable.
Do Flutter or React Native provide offline resilience automatically?
No. Both can support the architecture, but your app still needs persistence, meaningful state, an error policy and a synchronisation contract. Choosing a framework does not make a business workflow recoverable.
Can synchronisation be seamless without risking data loss?
It can be unobtrusive when actions have durable storage, safe retries and compatible server state. Conflicts, expired permissions and ambiguous outcomes sometimes require visible intervention. Hiding those cases would make the experience less trustworthy.
Mobile architecture modernisation
If your app loses a user’s work when the backend becomes unavailable, the next improvement may belong in the client architecture and API contract together.
Zimozi offers mobile architecture modernisation for Product Owners and Technical Directors building enterprise mobile applications. Start with one critical workflow and review its offline dataset, Flutter or React Native state model, error handling, queued writes and reconciliation process. The proposed scope is a practical resilience plan and a tested recovery path, grounded in your existing app.



