This roadmap breaks the project into small milestones. Each milestone should be implemented, documented, verified, committed, and then paused for review.
Repository note: Git was initialized after the first planning step so milestones can be committed one by one.
For every implementation milestone:
- Keep the change small and reviewable.
- Add or update
docs/learning/mN-milestone-name.md. - Explain possible approaches in simple English.
- Include advantages and disadvantages.
- Include at least one simple diagram.
- Include interview questions for junior, mid-level, senior, and architect levels.
- Run relevant tests or Gradle checks when possible.
- Create one Git commit for the milestone after reviewable work is complete.
- Stop and wait for user review.
Build a small "Field Notes" app.
Users can create, edit, and sync notes while moving between online and offline states. This domain is simple enough for learning, but rich enough to demonstrate local-first reads, offline writes, background sync, retries, conflict handling, and observability.
Example note fields:
- Title
- Body
- Last edited time
- Sync status
- Remote ID
- Local pending operation
The UI calls the API directly and stores little or no local data.
Advantages:
- Simple to build at first.
- Fewer local data problems.
- Good when the app is useless without fresh server data.
Disadvantages:
- Poor offline experience.
- Screens break or become empty when network fails.
- Harder to support pending local writes.
The app fetches from network and keeps a local cache for faster reads.
Advantages:
- Better startup and repeat loading.
- Some data can still be shown offline.
- Easier than full offline-first sync.
Disadvantages:
- Often treats offline as a fallback, not the main design.
- User writes may still fail immediately when offline.
- Cache invalidation can become confusing.
The UI reads from a local database. User writes go to the database first. Sync code later exchanges changes with the server.
Advantages:
- Best user experience during unreliable network.
- Clear local source of truth.
- Works well with background sync and retry.
- Scales into real system design topics.
Disadvantages:
- More moving parts.
- Requires careful sync state modeling.
- Conflict handling must be designed, not ignored.
Chosen direction: Option 3, offline-first with a local database as the source of truth.
flowchart TD
UI["Compose UI"] --> VM["ViewModel"]
VM --> Repo["NotesRepository"]
Repo --> DB["Room Database"]
Repo --> Sync["Sync Engine"]
Sync --> API["Remote API"]
API --> Sync
Sync --> DB
DB --> Repo
Repo --> VM
VM --> UI
The important idea: the UI observes local data. The network updates local data through sync.
Files:
agent.mdroadmap.mddocs/learning/m1-document-requirements-and-roadmap.md
Learning goal:
- Define the project contract and explain what offline-first means before writing code.
Expected implementation:
- Documentation only.
Commit:
m1: document roadmap and agent requirements
Status:
- Ready for commit after
agent.md,roadmap.md, and the M1 learning doc are added.
Learning goal:
- Create a clean Compose screen structure for a notes app without persistence yet.
Expected implementation:
- App scaffold.
- Notes list screen.
- Empty state.
- Add/edit note UI with in-memory state only.
Learning topics:
- Compose state basics.
- UI state vs business state.
- Why an app shell should be separated from data decisions.
Status:
- Implemented with an in-memory Compose Field Notes shell.
- Verified with
./gradlew testDebugUnitTest.
Learning goal:
- Introduce a maintainable package structure and ViewModel-driven UI state.
Expected implementation:
ui,domain,datapackage structure.- ViewModel with immutable UI state.
- UI events modeled clearly.
Learning topics:
- MVVM.
- Unidirectional data flow.
- State holders.
- Why screens should not know storage details.
Status:
- Implemented with
domain,ui.notes, immutable UI state, UI events, and aNotesViewModel. - Verified with
./gradlew testDebugUnitTest.
Learning goal:
- Make Room the source of truth for notes.
Expected implementation:
- Room entity.
- DAO.
- Database.
- Repository reads notes from Room as
Flow. - UI observes database-backed state.
Learning topics:
- Local-first reads.
- Entities vs domain models.
- Flow from Room.
- Why local storage is central to offline-first design.
Status:
- Implemented Room as the local source of truth behind
NotesRepository. - Verified with
./gradlew testDebugUnitTest.
Learning goal:
- Save user writes locally first and show whether each note is synced.
Expected implementation:
- Add sync status fields.
- Create and edit notes while offline.
- Show statuses such as
Pending Create,Pending Update,Synced, andFailed.
Learning topics:
- Optimistic writes.
- Pending operations.
- User trust and visible sync state.
Status:
- Implemented typed sync status and pending operation metadata.
- Verified with
./gradlew testDebugUnitTest.
Learning goal:
- Add a simple remote source without making the app depend on real internet.
Expected implementation:
- Fake API interface.
- Fake network delay and failures.
- Repository or sync component can call the fake API.
Learning topics:
- Remote data source abstraction.
- Why fake APIs are useful for learning and tests.
- Network-first vs database-first control flow.
Status:
- Implemented an in-memory fake notes API with delay and failure simulation.
- Verified with
./gradlew testDebugUnitTest.
Learning goal:
- Push pending local changes to the fake remote source and pull remote changes back into Room.
Expected implementation:
- Sync button.
- Push pending creates and updates.
- Pull remote notes.
- Update local sync statuses.
Learning topics:
- Sync loop basics.
- Idempotency.
- Mapping local IDs to remote IDs.
- Failure handling.
Status:
- Implemented manual push/pull sync through Room and the fake API.
- Repository sync is protected with a Kotlin
Mutexso manual sync and background sync cannot push the same pending operation at the same time. - Verified with
./gradlew testDebugUnitTest.
Learning goal:
- Run sync reliably outside the visible screen.
Expected implementation:
- WorkManager sync worker.
- Network constraints.
- Retry policy.
- Manual trigger plus background trigger.
Learning topics:
- WorkManager.
- Backoff.
- Constraints.
- Why background sync is not just a coroutine.
Status:
- Implemented one-time constrained WorkManager sync scheduling after local saves.
- Auto sync now also queues existing syncable pending notes when the user turns it on.
- WorkManager sync is network-constraint based, not periodic/time based.
- Verified with
./gradlew testDebugUnitTest.
Learning goal:
- Handle deletes safely in an offline-first system.
Expected implementation:
- Local delete operation.
- Tombstone state for pending remote delete.
- Sync delete to remote.
- Hide or display deleted items deliberately.
Learning topics:
- Why deletes are harder offline.
- Tombstones.
- Soft delete vs hard delete.
Status:
- Implemented local delete, tombstones, remote delete API, and sync cleanup.
- Verified with
./gradlew testDebugUnitTest.
Learning goal:
- Show what happens when local and remote versions both change.
Expected implementation:
- Version or timestamp fields.
- Conflict state.
- Demo conflict scenario.
Learning topics:
- Last-write-wins.
- Manual merge.
- Server-authoritative conflict resolution.
- Client-authoritative conflict resolution.
Status:
- Implemented timestamp-based conflict detection with local conflict metadata.
- Added a visible Remote screen so demos can edit the fake server copy directly.
- Verified with
./gradlew testDebugUnitTest.
Learning goal:
- Let users understand and resolve conflicts.
Expected implementation:
- Conflict screen or dialog.
- Choose local version, remote version, or merged version.
- Save resolved note and sync again.
Learning topics:
- Human-centered conflict handling.
- Data loss risks.
- Product choices in system design.
Status:
- Implemented keep-local, use-remote, and merge-both conflict resolution controls.
- Merge both combines local and remote text into one local pending update, then pushes that merged note on the next sync.
- Verified with
./gradlew testDebugUnitTest.
Learning goal:
- Display network availability without making connectivity the only source of truth.
Expected implementation:
- Connectivity observer.
- Online/offline indicator.
- Sync behavior that reacts to connectivity changes.
Learning topics:
- Connectivity is a hint, not a guarantee.
- Captive portals and flaky networks.
- Why all network calls still need error handling.
Status:
- Implemented connectivity observer and online/offline sync panel status.
- Verified with
./gradlew testDebugUnitTest.
Learning goal:
- Prove important offline-first behavior with tests.
Expected implementation:
- DAO tests.
- Repository tests.
- Sync tests with fake API.
- Failure and retry tests where practical.
Learning topics:
- Testing with fakes.
- Deterministic sync tests.
- What should and should not be tested on Android device/emulator.
Status:
- Added fast offline-first behavior tests for core sync state transitions.
- Verified with
./gradlew testDebugUnitTest.
Learning goal:
- Make sync behavior easier to inspect while learning.
Expected implementation:
- Debug sync log screen.
- Last sync result.
- Last sync time.
- Error messages suitable for education.
Learning topics:
- Observability.
- Debugging offline systems.
- Logs vs user-facing status.
Status:
- Implemented in-app rolling sync debug log.
- Verified with
./gradlew testDebugUnitTest.
Learning goal:
- Review the complete app as a system design case study.
Expected implementation:
- Clean up naming and UI.
- Review docs.
- Final architecture diagram.
- Final interview question bank.
Learning topics:
- Offline-first tradeoffs.
- Mobile system design.
- Scaling from demo to production.
Status:
- Completed final README and architecture review.
- Added later UI polish: notes list with floating action button, dedicated create/edit screen, adaptive long-note layout, delete confirmation, stronger sync CTA, multi-screen tabs, and architecture learning updates.
- Verified with
./gradlew testDebugUnitTest.
Learning goal:
- Keep the app demo-ready while explaining advanced offline-first concepts in simple English.
Implemented refinements:
- Notes screen became list-first, with a
+floating action button and dedicated editor screen. - Note cards and text fields now adapt to short and long content.
- Delete asks for confirmation before changing local state.
- Remote notes can be edited directly from the Remote screen for conflict demos.
- Merge-both conflict resolution was added as the preferred demo path.
- Auto sync queues existing syncable pending notes when enabled.
- WorkManager remains one-time constrained work with
NetworkType.CONNECTED; it is not a periodic timer. - Repository sync uses a
Mutexto serialize sync calls from manual sync and WorkManager.
Advanced concepts to review:
Mutexfor sync serialization.- WorkManager unique one-time work.
- Network constraints instead of polling.
- Tombstones for safe offline delete.
- Conflict metadata for durable resolution.
- Local source of truth with Room and Flow.
Learning goal:
- Replace manual object construction with Hilt dependency injection.
Expected implementation:
- Add Hilt Gradle plugin and dependencies.
- Add
@HiltAndroidAppapplication class. - Add
@AndroidEntryPointactivity. - Add Hilt modules for Room, fake API, WorkManager, repository, connectivity, and sync scheduler.
- Convert
NotesViewModelto@HiltViewModel. - Convert
NotesSyncWorkerto@HiltWorker. - Remove the manual
AppContainerand custom ViewModel factory.
Learning topics:
- Constructor injection.
- Modules and bindings.
- Singleton-scoped app dependencies.
- Hilt ViewModel injection.
- Hilt WorkManager integration.
- Testability with small interfaces.
Status:
- Implemented Hilt dependency injection and removed manual DI boilerplate.
- Verified with
./gradlew testDebugUnitTest.
All planned milestones are complete.
docs/learning/m1-document-requirements-and-roadmap.mddocs/learning/m2-baseline-app-shell.mddocs/learning/m3-architecture-packages-and-ui-state.mddocs/learning/m4-local-source-of-truth-with-room.mddocs/learning/m5-local-writes-and-sync-status.mddocs/learning/m6-fake-remote-api.mddocs/learning/m7-manual-sync.mddocs/learning/m8-background-sync-with-workmanager.mddocs/learning/m9-delete-and-tombstones.mddocs/learning/m10-conflict-detection.mddocs/learning/m11-conflict-resolution-ui.mddocs/learning/m12-connectivity-awareness.mddocs/learning/m13-testing-offline-first-behavior.mddocs/learning/m14-observability-and-debug-tools.mddocs/learning/m15-final-polish-and-architecture-review.mddocs/learning/m16-dependency-injection-with-hilt.md
The user requested one commit per micro milestone. Use this pattern:
git add .
git commit -m "mN: short milestone name"Use the local Git identity requested by the user:
git config user.name "venkataramk"