This project demonstrates a simple offline-first mobile application that allows users to submit two types of data:
- Small data (text input)
- Large data (image selection)
All user actions are persisted locally first, reflected immediately in the UI, and then safely synced to a backend when network connectivity is available.
The focus of this implementation is correct offline behavior, reliability, and clarity, not visual polish.
-
Local-first
- User actions are saved locally immediately.
- The UI updates optimistically without waiting for the network.
-
Deferred sync
- Network sync happens only when connectivity is available.
- Sync retries automatically on app start or reconnect.
-
Small before Large
- Small payloads are always synced before large payloads.
- This improves reliability under poor network conditions.
-
Single source of sync control
- Synchronization logic is centralized in a global context.
- Screens never manage network or sync state directly.
- User submits data (Small or Large)
- Data is validated locally
- Data is stored in persistent storage with status
pending - UI immediately shows “Saved (pending sync)”
- When online:
- Pending items are loaded
- Items are sorted (Small → Large)
- Items are synced sequentially
- On success:
- Item is removed from the local queue
- UI updates to “Saved”
A global SyncContext is responsible for:
- Listening to network connectivity changes
- Triggering sync on:
- App start
- Offline → online transitions
- Ensuring only one sync runs at a time
Screens interact with sync only via:
requestSync()This keeps UI components simple and avoids duplicated sync logic.
- Retry attempts are tracked only in memory during a single sync run
- If an item fails 3 times in a row:
- It is skipped
- Left in the queue
- Retried on the next sync attempt
- Failed items are never deleted unless a sync succeeds
This avoids:
- Infinite retry loops
- Permanently “poisoning” valid data
- App killed during sync
- Network loss mid-request
- Duplicate retries after partial failures
- Device restart while offline
- Multiple rapid user submissions
- Flaky or slow connections
All queued data remains safe until successfully synced.
To keep the solution focused and clear:
- No background services
- No conflict resolution or CRDTs
- No persisted retry counters
- No chunked uploads for large data
- No backend implementation (simulated sync)
These were consciously excluded to avoid over-engineering for a take-home task.
- Simple, readable, and reliable
- Correct offline-first behavior
- Easy to extend
- Clear separation of concerns
- Large payloads stored as base64 (not ideal for production)
- No resume support for partial uploads
- No backend idempotency implementation (not required here)
Given more time or production requirements, this system could be improved by:
- Storing large files on disk instead of base64
- Using resumable uploads for large data
- Adding backend-side idempotency keys
- Introducing background sync (platform-specific)
- Adding sync progress indicators per item
- Node.js
- Yarn or npm
- React Native environment set up
npm install
# or
yarn installnpx react-native run-ios
# or
npx react-native run-androidThis application is best tested on a real device, especially for offline behavior and network state changes. Simulators may not accurately reflect real-world connectivity conditions.
- Launch the app
- Enable airplane mode
- Add Small and Large items
- Kill the app
- Reopen the app (data persists)
- Disable airplane mode
- Observe automatic sync
The diagram illustrates:
- Local queue
- Sync triggers
- Small vs Large prioritization
- Retry flow
- Network-driven sync lifecycle
This implementation prioritizes correctness, simplicity, and real-world behavior over complexity.
All major architectural decisions are intentional and aligned with offline-first principles.
