APK: submission/CourseFlow.apk Demo video (2 min): submission/CourseFlow-demo.webm
Learning dashboard app: Login → Course Dashboard → Course Details, with offline support. Stack: Kotlin · XML views + ViewBinding · MVVM · Coroutines/StateFlow · Room · EncryptedSharedPreferences · JUnit
Open in Android Studio (JDK 17) → Sync → Run. Tests: ./gradlew testDebugUnitTest. Min SDK 24.
- Login: any valid email + password of 6+ characters. Password
wrong123shows the error state. - Session: the token is saved, so later launches open the dashboard directly (also offline). Menu (⋮) → Log out.
- Offline demo: log in and let the courses load → airplane mode → relaunch → courses and lessons still show, and lessons can still be completed.
MVVM + Repository: Activity → ViewModel (StateFlow) → Repository → Room + remote data source. Each screen renders one
immutable state (Loading / Success / Empty / Error), so rotation and process death are safe and logic is testable without Android.
domain/ holds pure logic (ProgressCalculator, InputValidator). The mock API sits behind an interface, so Retrofit can replace it
without touching upper layers. Manual DI via ServiceLocator keeps the 3-hour scope small (Hilt in production).
Room is the single source of truth; the UI only observes database Flows. refresh() syncs remote → local in one transaction and
caches courses with their lessons, so list and details both work offline. If a refresh fails while the cache has data, the list stays visible
with an "Offline" banner; the Error state appears only when nothing is cached. Refresh never overwrites the user's local completions.
Marking a lesson done updates the lesson and course progress in one DB transaction.
In production the token belongs in the Android Keystore-backed encrypted storage (this app uses EncryptedSharedPreferences), never plain
SharedPreferences. Use short-lived access tokens plus a refresh token, attach via an OkHttp interceptor, clear on logout/401, disable
backups, and use TLS with certificate pinning.
- Paginated, incremental sync (Paging 3 +
RemoteMediator, ETags /updatedSince) instead of fetching everything. - Sync lesson progress to the backend through a WorkManager outbox (retry/backoff, idempotent writes, conflict rules).
- CDN and HTTP caching for the catalog and images; server-side pagination and rate limiting.
- Observability: Crashlytics, performance traces, API error/latency metrics, feature flags, staged rollouts.
- Hilt, feature modules, CI with UI tests, and proper Room migrations (
exportSchemais off in this demo).
Same layers in Swift: SwiftUI views → @Observable view models (≈ ViewModel + StateFlow) → repository protocol → URLSession +
SwiftData/Core Data as the offline source of truth. Same state enum, async/await, NWPathMonitor for connectivity, token in the
Keychain, and ProgressCalculator ported 1:1 with XCTest. Kotlin Multiplatform could share the domain/data layer.
ProgressCalculatorTest covers percentage rounding, empty courses, clamping and seeding from server progress; InputValidatorTest covers login rules.
Mock API and login (fake token, no refresh/expiry); no DI framework; lessons can only be marked completed (not undone); logout keeps cached courses.