Kanarek is one Android application with two user-facing domains and an optional edge backend.
Repository boundaries#
| Component | Responsibility |
|---|---|
app/ | Android UI, DataStore, WorkManager, services, notifications, launcher widgets |
shared/ | portable models, parsers, codecs, merge/state logic and shared UI state |
worker/ | optional edge parsing, caching, discovery, scraping, article extraction and synchronized state |
Reader path#
When a backend is configured, NewsRepository can request normalized merged data from the Worker and reuse responses with ETags. If the backend is blank or unavailable, feeds are downloaded concurrently and parsed by the shared Kotlin feed parser.
One broken feed should not poison the whole batch, and unsuccessful refreshes do not intentionally erase last-known-good widget data.
Playback path#
PlayerService owns ExoPlayer and the app's MediaSession, so playback survives navigation and remains available to Android media controls. Widgets cannot keep the Activity's local binder, so they communicate through service actions and playback snapshots.
Widgets#
Both launcher widgets use RemoteViews, not Compose. Widget images are stored in a dedicated on-disk cache rather than relying on an Activity-scoped image loader.
Source-of-truth rule#
Portable parsers, models and state transformations belong in shared/commonMain when possible. Android-specific code should keep lifecycle-sensitive responsibilities rather than grow duplicate domain logic.