
An offline-capable mobile application needs to show what is stored locally, what is waiting to be sent and what the server has accepted. Treat synchronization as an explicit workflow. A temporary loss of connectivity should leave the user with understandable state and a recoverable operation.
Define local truth and pending work
For a fictional field-notes app, a user edits a note while disconnected. Save the visible note and a pending operation in one local transaction so that a crash cannot leave a visible edit with no record of work to send. Store an operation identifier, entity identifier, base server version, payload and state such as queued, sending, acknowledged or conflict.
The Android offline-first architecture guide explains local data sources and synchronization choices. In a Java-based Android app, a local database and repository can provide a consistent view to the UI. Select components and versions that support the project’s language and build configuration; the architecture itself is broader than a particular SDK.
Display “saved on this device,” “waiting to sync” and “synced” as different states. Store the last confirmed server version and refresh time. For sensitive data, define local access, retention, logout cleanup and backup policy before adding offline storage.
Make a lost reply safe to retry
Suppose operation edit-A changes server version 7 to version 8. The server commits, but the response is lost. The phone still has the queued operation. Retrying the same identifier and payload must return the recorded result instead of applying the change again.
Deduplication belongs on the server as well as the client. Bind the identifier to the user and operation payload, store the effect and its result consistently, and choose a retention period covering the retry window. Reject reuse of the same identifier with different content. A local “sent” flag cannot prove whether the server committed before a disconnect.
Run a small synchronization model
The downloadable Java 25 program models a queue and server entirely in memory. It deliberately loses the first success reply, retries, then introduces a stale edit from another device.
Download the source, checks and README (ZIP)
javac SyncLab.java
java SyncLabapplied
already applied
conflict
id reused with different payload
server version=8, text=Local draftRead the source in this order: the Edit record, the server’s duplicate check, its version check, and the queue removal after acknowledgement. The repeat changes neither the version nor the text. The stale version-7 edit is retained as a conflict for the caller to resolve.
The model is single-threaded and uses memory only. A deployed server needs durable, atomic checks and updates; a mobile client needs a persistent queue that survives process death. This exercise verifies protocol decisions, not Android lifecycle behavior or production database isolation.
Choose a conflict policy with the user’s task in mind
On narrow screens, scroll this table horizontally. Keyboard users can focus it and use the arrow keys.
| Situation | Useful policy | Visible outcome |
|---|---|---|
| Same operation delivered again | Return its stored result after verifying the payload | The original edit becomes acknowledged once. |
| Two devices edit version 7 | Reject stale overwrite; fetch version 8 and offer a merge or explicit choice | User sees both relevant versions. |
| Authentication expires | Pause sending and request reauthentication | Local work remains visible and pending. |
| Server temporarily unavailable | Bound retries with backoff and a retry budget | Queued status and a useful retry path. |
| Deletion races with an offline edit | Define tombstones and a restoration policy | The app explains whether the item was deleted or restored. |
Last-write-wins can discard a useful edit, and client clocks may differ. Choose it only where that loss is an acceptable product decision. For structured data, a domain-specific merge may preserve independent fields. For an important note, an explicit conflict screen can be easier to trust than an invisible overwrite. The HTTP If-Match precondition is one way an HTTP API can guard a versioned update.
Use push as a signal to reconcile
A push message can tell the app that data may have changed. Fetch and reconcile from the authoritative service using the same synchronization rules; keep startup and periodic reconciliation paths for missed signals. Avoid using notification receipt as proof that all state is current.
Firebase’s Android message-receiving guidance describes foreground/background handling and processing constraints. WorkManager provides Android scheduling for persistent work, subject to constraints and platform behavior. Test the selected versions on real target devices, including background and battery conditions.
Before release, test airplane mode, process termination after a local save, a lost acknowledgement, duplicate delivery, stale credentials, a conflicting edit and logout with queued work. Verify both the stored records and the messages the user sees. The historical Go.Web approach also addressed offline content and alerts; these recurring responsibilities remain useful even as APIs and mobile platforms change.