Flutter Offline-First Task Manager with Sync
Build a Flutter task manager that works completely offline and syncs with a server when the connection returns. Tasks are stored on the phone in an Isar database, so the app is instant and always available; every change is also recorded as pending, and when connectivity_plus reports that the phone is back online, a sync service sends pending changes to a REST API and fetches changes made on other devices. When the same task was edited in two places, the most recent edit wins (last-write-wins), decided consistently by timestamps. The screen always shows whether everything is synced. You'll learn offline-first design, local databases, Riverpod, sync protocols, conflict resolution and its trade-offs, and testing sync logic.
- Estimate
- ~14.5h
- Steps
- 7
- Completed by
- 0
- Proposed by
- codeseed.app
Flutter · Isar · Riverpod · Connectivity Plus
Project roadmap
- 01
Set up local storage
~2hCreate the Flutter project with isar, riverpod and connectivity_plus. Define an Isar Task collection: a UUID made on the device, title, notes, done, due date, updated at (set on every change), a deleted flag (so deletions can be synced), and a sync status (synced or pending). Run the code generator. Done when: tasks can be saved and read back from Isar.
- 02
Build the task screens
~2.5hCreate a Riverpod repository and providers that watch Isar queries, so the list updates by itself. Build the task list (with filters for all, active and done), and screens to add, edit, complete and delete tasks. Every change sets updated at and marks the task pending; deletions only set the deleted flag. Done when: the app works fully in airplane mode.
- 03
Build a simple sync server
~2hWrite a small REST server (for example in Dart with shelf, or Node.js) with POST /sync/push (receive changed tasks, keeping each one only if its updated at is newer than the stored one) and GET /sync/pull?since= (return tasks changed after a time, including deleted ones, and the server's current time as the next cursor). Store tasks in SQLite. Done when: the server's API works with curl.
- 04
Implement the sync service
~2.5hWrite a SyncService that pushes pending tasks, marks the accepted ones as synced, then pulls changes since the saved cursor and applies each one locally only if it's newer than the local copy (last-write-wins), all without overwriting edits made during the sync. Save the new cursor only after everything is applied. Prevent two syncs from running at once. Done when: changes made on two emulators end up on both.
- 05
Sync on reconnect
~1.5hListen to connectivity_plus changes and start a sync when the connection returns, and also after local changes (a few seconds later, grouping quick edits) and when the app opens. Since having a network doesn't guarantee reaching the server, treat failed requests as offline and retry with increasing waits. Done when: edits made offline sync by themselves shortly after reconnecting.
- 06
Show sync status
~1.5hShow the sync state in the app bar (synced, syncing, pending changes or failed) with the last successful sync time, and a manual Sync now button. Mark pending tasks with a small icon. Show errors in a snackbar with Retry. Done when: the status always reflects reality.
- 07
Test the sync logic
~2.5hWrite tests with a fake server and a test Isar instance: pending changes are pushed and marked synced, pulled changes apply only when newer, deletions sync, edits during a sync aren't lost, and the cursor isn't saved after a failed pull. Add a test with two simulated devices editing the same task offline. Document the trade-off of last-write-wins (an older edit is discarded) in README.md. Done when: all tests pass.
Ready to build this?
Get a GitHub repo and start building. Your AI reviewer checks each step as you go.
Tech stack