Offline-First Field Survey
Build an Android app in Kotlin for people who collect survey data in the field (inspectors, researchers, volunteers) where the internet connection is often missing. Surveyors fill in forms that are checked on the phone right away, and completed submissions are saved in a Room database, encrypted, and queued. When the phone is online, WorkManager uploads the queue in the background to a Ktor server, retrying failures, without ever sending a submission twice, even if the connection drops in the middle of an upload. You'll learn offline-first architecture, Room, encrypting data at rest, WorkManager for reliable background work, idempotent uploads, and building a small Ktor backend to receive the data.
- Estimate
- ~18h
- Steps
- 7
- Completed by
- 0
- Proposed by
- codeseed.app
Jetpack Compose · Room · Ktor
Project roadmap
- 01
Design the system
~2.5hWrite ARCHITECTURE.md describing: the survey form (defined as JSON with fields such as text, number, choice, date and required rules), the submission lifecycle (draft, queued, uploading, uploaded, rejected), what's stored and encrypted on the phone, and the upload API. Each submission gets a random UUID when created, used as its idempotency key. Done when: the document covers every state and what happens when an upload fails at each point.
- 02
Build the Ktor server
~2.5hBuild a small Ktor server with POST /submissions that validates the submission against the same form rules and stores it (in a database or file). When the same UUID is sent again, return the original result without saving a second copy. Return 400 with field errors for invalid data, and require an API token. Done when: sending the same submission twice stores it once, and both calls succeed.
- 03
Render and validate forms
~3hIn the Android app, render the form from its JSON definition with Jetpack Compose: the right input for each field type, required markers, and error messages shown under each field as the user types. Save drafts automatically so nothing is lost if the app closes. Share the validation rules with the server logic, so both agree. Done when: the form can't be submitted with invalid data, and drafts survive closing the app.
- 04
Store submissions encrypted
~2.5hSave submissions in Room with their status and attempt count. Encrypt the answers before storing them, using a key kept in the Android Keystore (for example with Google Tink or javax.crypto AES-GCM), so they can't be read by copying the database file. Show the queue with each submission's status. Done when: the database file contains no readable answers, and the app still shows them correctly.
- 05
Upload with WorkManager
~3hCreate a CoroutineWorker that uploads queued submissions one by one with the Ktor client, scheduled with a network-connected constraint and exponential backoff. Mark a submission uploaded only after the server confirms. Mark 400 responses as rejected with the reason (retrying won't help), and retry network errors and 5xx responses. Done when: submissions made offline upload by themselves once the phone is back online.
- 06
Handle interruptions and edge cases
~2hMake sure an upload interrupted after the server saved it but before the phone got the answer is simply retried and recognized by its UUID. Recover submissions stuck in uploading when the app restarts. Let the user fix and resend rejected submissions. Show the time of the last successful sync. Done when: turning airplane mode on during uploads never creates duplicates on the server.
- 07
Test it
~2.5hWrite tests: the form validation rules, Room DAO tests, a WorkManager test with TestListenableWorkerBuilder against a fake server that fails, times out or accepts, and Ktor server tests with testApplication for idempotency and validation. Done when: all tests pass, and a manual airplane-mode test uploads everything exactly once.
Ready to build this?
Get a GitHub repo and start building. Your AI reviewer checks each step as you go.
Tech stack