Educational Android demo app for learning offline-first system design.
The app is a small Field Notes tool built with Kotlin, Jetpack Compose, Room, Flow, WorkManager, Hilt, and a fake remote API. It is intentionally implemented through micro milestones so each architectural idea can be reviewed in Git history and in docs/learning/.
- Local database as the source of truth.
- Hilt dependency injection instead of a manual app container.
- Local writes before network sync.
- Visible sync status.
- Pending create, update, and delete operations.
- Manual sync.
- Background sync with WorkManager.
- Auto sync that queues existing pending notes when enabled.
- Tombstones for offline deletes.
- Fake remote API.
- Conflict detection.
- Conflict resolution with keep local, use remote, and merge both.
- Connectivity awareness.
- Dedicated create/edit note screen with a notes list and floating action button.
- Delete confirmation before removing a note.
- Debug sync log.
- Serialized sync with a Kotlin
Mutexso manual sync and background sync do not race. - Fast unit tests for offline-first behavior.
flowchart TB
A["User writes a note"]
B["Save immediately<br/>to local database"]
C["Show note from<br/>local database"]
D["Sync when network<br/>is available"]
E["Remote server copy"]
F{"Conflict?"}
G["Mark note synced"]
H["Resolve conflict<br/>merge or choose version"]
I["Save resolved note<br/>locally"]
J["Show latest note<br/>from local database"]
A --> B --> C --> D --> E --> F
F -->|No| G --> J
F -->|Yes| H --> I --> J
The key rule is simple:
The app saves first, shows local data immediately, and syncs later when the network is available.
com.fieldnotes.app
+-- data
| +-- connectivity # Online/offline observer
| +-- local # Room database, DAO, entities
| +-- remote # Fake remote API and remote models
| +-- sync # WorkManager worker and sync scheduler
+-- di # Hilt modules and dependency bindings
+-- domain # App models, repository contract, sync states
+-- ui
+-- notes # Compose screens, route, ViewModel, UI events/state
+-- theme # Compose Material theme
Main idea:
uirenders state and sends user events.domaindefines stable app concepts.dataimplements storage, fake remote, connectivity, and sync.diwires dependencies with Hilt.
- Open
Notesand tapCreate local noteor the+floating action button. - Save the note from the dedicated editor screen.
- See it marked as
Pending create. - Tap
Sync pending changes. - See it become
Synced. - Edit the note.
- See it become
Pending update. - Open
Remoteand edit the fake server copy. - Open
Notes, edit the same local note differently, and save. - Open
Sync, tapSync pending changes, and review conflict behavior. - Choose
Merge bothto combine local and remote text, or chooseKeep local/Use remoteto compare strategies. - Enable auto sync and create/edit a pending note. WorkManager queues one-time sync work that waits for network if needed.
- Delete a synced note, confirm deletion, and observe tombstone-driven sync behavior.
Auto sync is not a timer in this project.
- The app schedules one-time WorkManager work.
- The work has a
NetworkType.CONNECTEDconstraint. - If network is unavailable, WorkManager waits.
- When network is available, Android can run the queued sync.
- If auto sync is turned on while syncable pending notes already exist, the app queues work immediately.
- Conflict notes are not auto-synced until the user resolves the conflict.
Learning notes live in docs/learning/.
Each milestone includes:
- Goal.
- What changed.
- Why it matters.
- Possible solutions.
- Advantages and disadvantages.
- Simple diagram.
- Android best practices.
- Verification.
- Junior, mid-level, senior, and architect interview questions.
Run:
./gradlew testDebugUnitTest




