Help us measure site usage?

With your permission, we use self-hosted analytics to understand page usage. It is optional and does not affect the Service. Learn more.

KotlinadvancedmobileAI generated

Background Sync Engine

Build the synchronization engine of an Android app that works offline: users change data on the phone at any time, and the engine keeps it in sync with a server in the background. Every change is saved locally in Room first and recorded in an outbox; a scheduled WorkManager job sends pending changes to a Ktor REST API and fetches changes made elsewhere. Each record carries a version number, so when the same record was changed on two devices, the conflict is detected and resolved by clear rules instead of one change silently overwriting the other. Syncs survive the app being closed, retry with increasing waits, and never apply the same change twice. You'll learn offline-first data flow, outbox and version tokens, conflict resolution, WorkManager, and testing sync logic.

Estimate
~16.5h
Steps
7
Completed by
0
Proposed by
codeseed.app

Jetpack Compose · Room · Ktor

Project roadmap

  1. 01

    Design the sync protocol

    ~2.5h

    Write SYNC.md for a simple data type such as notes or tasks: each record has an id (a UUID made on the device), a version number set by the server, updated at and a deleted flag. The API has POST /sync/push (a batch of changes with the version each was based on) and GET /sync/pull?since=cursor (changes after a cursor). Define the conflict rules, for example the server version wins for some fields and the latest edit for others, and deletes win. Done when: SYNC.md covers push, pull, cursors and every conflict case.

  2. 02

    Build the Ktor server

    ~2.5h

    Build the API with Ktor: push applies each change only if its base version matches the stored version, bumps the version and returns the new one, otherwise returns a conflict with the current record. Change ids make pushes idempotent, so resending a batch changes nothing. Pull returns changes in order with the next cursor. Store data in a database with Exposed or plain JDBC. Done when: API tests show conflicts detected and repeated pushes ignored.

  3. 03

    Save locally with an outbox

    ~2.5h

    In the app, save every user change to Room and add an outbox row (change id, record id, operation, payload, base version) in the same transaction, so a change is never saved without being queued. Show the data in a Jetpack Compose list with a small marker for records not yet synced. Done when: changes made in airplane mode appear at once and are marked unsynced.

  4. 04

    Sync with WorkManager

    ~2.5h

    Create a CoroutineWorker that pushes outbox entries in batches, removes accepted ones and updates their versions, then pulls remote changes from the saved cursor and applies them in a transaction. Schedule it periodically and after local changes, with a network constraint and exponential backoff, as unique work so two syncs never run at once. Done when: changes made offline reach the server once the phone is online, and remote changes appear locally.

  5. 05

    Resolve conflicts

    ~2.5h

    Apply the rules from SYNC.md when the server reports a conflict: merge automatically when possible, and otherwise keep the server version and save the local version as a conflict copy that the user can review in a Conflicts screen and choose from. Never lose either version silently. Done when: editing the same record on two emulators while offline ends with a correct merge or a visible conflict.

  6. 06

    Recover from interruptions

    ~1.5h

    Make sure an interrupted sync (the app killed after pushing but before recording the result) is safe: the next push is recognized by change ids. Only move the pull cursor after changes are saved. Show the last successful sync time and the number of pending changes, and log each sync's results. Done when: killing the app during a sync never creates duplicates or skips remote changes.

  7. 07

    Test the sync logic

    ~2.5h

    Write tests: the conflict rules as pure functions, Room outbox transactions, the worker against a fake server that fails, times out, or reports conflicts, and Ktor API tests with testApplication. Add a test simulating two devices syncing random changes until both match the server. Done when: all tests pass, and the two-device test always ends in agreement.

Ready to build this?

Get a GitHub repo and start building. Your AI reviewer checks each step as you go.

~16.5h · 7 steps

Tech stack

Jetpack ComposeRoomKtor