DEV Community

Salil Harit
Salil Harit

Posted on Fully Autonomous

Designing a SQLite outbox for field operations in Flutter

When a field worker enters operational information, the valuable part is the work they have already done. Connectivity should not be the only thing keeping that work alive.

In SEC ERP's mobile app, I designed a buffered-write path around that requirement. Flutter handles the interaction, SQLite preserves the request, and Supabase processes the cloud write.

Persist intent before transport

The buffer inserts a queue entry before sending. It records the originating account, operation, payload, creation time, and status. If connectivity is unavailable, the request stays pending. A conditional claim moves an eligible entry to IN_FLIGHT before processing.

Compared with keeping retries in memory, this adds persistence and queue management. I chose that complexity because recovery needs a record that survives beyond the request's lifetime.

Make retries an architectural decision

A response can disappear after the server accepts an insertion. Sending again can create another record. Automatically retrying every error would turn a network problem into a data-quality problem.

The buffer classifies failures: recognized rejections are retained for correction, uncertain INSERT failures require review, and other eligible failures enter RETRYING.

The tradeoff is deliberate. Some requests need review instead of automatic completion, but entered work remains available. Where automatic insertion replay is required, stable request identities and server deduplication provide a stronger contract.

Attach recovery to the originating account

Queue entries carry an owner identifier. Reads and status updates use that owner, and processing checks account continuity around claims and transport.

This makes ownership part of persistence. Switching accounts does not transfer the previous account's pending work to the new user.

Expired claims also have explicit recovery rules: UPDATE operations return to RETRYING, while other expired in-flight operations require review. Recovery follows the operation type.

Separate confirmation from caching

Duty activation shows why one success flag is insufficient. Supabase can confirm activation while the device fails to cache the returned record.

The provider validates and publishes the confirmed duty, then attempts local mirroring. A mirror failure is reported separately while confirmed duty remains in live state. Recovery can focus on device storage rather than repeating an already-confirmed action.

Validate the failure boundaries

Regression cases challenge zero-row updates, wrong returned targets, rejected activation, and accepted activation followed by local persistence failure.

Each protects a business meaning across a failure boundary. The result is an architecture where field work has a durable home, retries have explicit rules, and cloud confirmation remains distinct from device storage.

Top comments (1)

Collapse
 
spydon profile image
Lukas Klingsbo •

Good news! There is a plugin system coming to the supabase_flutter v3, and one of the plugins will handle this for you. You can write your own plugins too, so you could probably quite easily hook this one in there once it is released.