An Android app can save data without a connection and still synchronize it incorrectly when connectivity returns. The difficult case is a lost response: the server accepted the operation, but the phone never received confirmation. Sending a new operation during retry can create the same business result twice.

The practical design is to persist a local change and an outbox record together, schedule delivery with WorkManager, and retain the same operation identifier across retries. Scheduling work on the device does not replace duplicate protection in your API.

When an outbox is worth the extra state

An outbox is useful when people should continue working through interruptions: recording study progress, editing a draft, or updating a task list. Once the local transaction succeeds, the interface can show the change with an honest pending state.

Do not label a queued request as synchronized. Also separate operations that can wait from decisions requiring an immediate server answer. A product that depends on current availability needs a clear pending or unavailable state rather than a silent promise of success.

A local transaction moves through an outbox and worker before the server confirms it

Define the smallest useful record

Consider a hypothetical learning app saving the result of a practice session. Save the result and its outbox entry in one local transaction. If the process stops halfway through, both changes should be committed or neither should be committed.

FieldResponsibilityRule to establish
operation_idIdentifies one business actionGenerate once and preserve during retry
entity_idIdentifies the practice sessionDo not use a changing list position
payload_versionIdentifies the message formatSupport queued records during app upgrades
stateTracks pending, sending, confirmed or blockedConfirm only after an appropriate response
attempt_countSupports diagnosisNever use it to create a new operation identity

Keep unnecessary personal information out of the queue. Store the fields the API actually needs, define who can access them, and establish when confirmed entries are removed.

Give the worker an explicit response policy

Android documents network constraints and retry backoff for WorkManager. A connected device can still face an unavailable API, an expired session or a permanent validation error. Those conditions need different outcomes.

The following pseudocode describes responsibilities rather than a complete Worker implementation:

read an unconfirmed operation
send its saved payload and operation_id
if the server confirms that operation:
    mark it confirmed in a local transaction
if the failure is temporary:
    retain the identity and retry with backoff
if the payload is permanently rejected:
    mark it blocked and expose a recovery action

The server must coordinate duplicate detection with the business write. A separate check followed by an unprotected insert can still allow two concurrent requests through. Use a suitable transaction or uniqueness rule and return a consistent result for an already accepted operation.

For the learning example, distinguish a duplicate completion from an intentional new attempt. Reusing a session identifier for both would prevent a valid second attempt; generating a new identifier for every retry would fail to protect the first one.

Do not confuse conflicts with duplicate delivery

Two devices editing the same entity create a separate problem. Choose whether to merge changes, reject an outdated revision or ask the user to resolve a conflict. Repeated delivery of one operation does not establish which of two different edits should win.

Avoid making device clocks your only ordering authority. For some learning products, an immutable completed attempt is easier to reason about than repeatedly replacing a single progress object. Evaluate that choice against the actual product contract.

Test the points where ownership changes

  1. Disconnect after the user saves. The local result remains visible and pending.
  2. Let the server accept the request, then suppress its response. Retry produces one result.
  3. Stop the process during delivery. Reopening restores the pending queue.
  4. Expire authentication. Signing in again preserves the unsent operation.
  5. Upgrade while old payloads remain queued. They are delivered or converted deliberately.

Record the build, before-and-after states, operation identifier and response category. Exclude credentials and full user payloads from diagnostic logs. A screenshot of a success message is insufficient if the database contains duplicate completions.

For the wider release process, use the idea-to-release working checklist. For queued images, see the recoverable mobile upload design.

Frequently asked questions

Does unique work guarantee one server-side effect?

No. It helps coordinate work on a device. The API still needs a stable operation identity and duplicate-handling behavior.

When should an outbox entry be deleted?

After appropriate confirmation and according to a defined retention policy. Decide whether a short-lived confirmation record is needed for diagnosis before removing the evidence completely.

Primary sources

Check these original references again before making a release or policy decision.