Repository navigation
Architecture
boxlore is a multi-module Android project. :app is the shell, shared behaviour lives in :core:*, and screens live in :feature:*. Dependencies point inward, features never depend on each other, and one explicit container wires the whole app.
The repository's ARCHITECTURE.md is the source of truth for module boundaries and identity contracts. This page is a guided summary of it.
- Dependencies point inward Features depend on playback, catalog and lower core modules. Core modules never depend on features or UI.
-
Features stay isolated
A feature module may not declare a Gradle dependency on another feature or import its code. Flows that cross features go through the
:appnavigation graph. -
One composition root
AppContainerin:appbuilds the runtime object graph by hand. There is no Hilt, Dagger or Koin. -
Rules as code
Most boundaries are checked in CI by Konsist tests in
:core:testing. A few are checked by scripts or kept by convention; the list below says which.
boxlore/
├── app/ # Application shell, AppContainer, navigation host, FCM, WorkerFactory
├── core/
│ ├── model/ # Shared models and enums
│ ├── auth/ # Sign-in domain and Firebase Auth implementation (optional accounts)
│ ├── network/ # HTTP client for the boxlore API (BoxLoreApi) and network DTOs
│ ├── domain/ # Thin ports and small result types
│ ├── database/ # Main Room database (boxlore_database), entities, DAOs, migrations
│ ├── prefs/ # DataStore and SharedPreferences façades
│ ├── analytics/ # Analytics façade; features never call the vendor SDK directly
│ ├── catalog/ # PodcastRepository, subscriptions, content sections, backup and restore
│ ├── rss/ # RSS fetch and parse, direct RSS shows, episode supplements, rss:/negative IDs
│ ├── ranking/ # On-device re-ranking: LinUCB-style model, taste meters, own Room database
│ ├── downloads/ # Download repository, Smart Downloads and auto-download workers
│ ├── playback/ # PlaybackRepository, queue, Media3 services, Smart Queue helpers
│ ├── designsystem/ # Theme and shared composables; no data or network ownership
│ └── testing/ # Shared fixtures, fakes and architecture guards
└── feature/
├── home/ # Home feed, Daily and Offline mix
├── settings/ # Settings hub: account and cloud sync, appearance, playback, downloads, privacy
├── history/ # Listening history timeline and insights
├── player/ # Player overlay (PlayerSheetScaffold), chapters, transcripts
├── info/ # Show and episode pages, including deep links
├── explore/ # Explore, search, and Lore (named Learn in code)
├── library/ # Library hub: subscriptions, folders, downloads, liked
├── onboarding/ # First-run flows: AI suggestions, genres, search, import
├── briefing/ # The boxlore brief
└── widgets/ # Home-screen widgets (RemoteViews)
The folder path matches the Gradle id: core/playback is :core:playback.
flowchart TB
app[":app"]
subgraph features ["Features (isolated from each other)"]
home[":feature:home"]
settings[":feature:settings"]
history[":feature:history"]
player[":feature:player"]
info[":feature:info"]
explore[":feature:explore"]
library[":feature:library"]
onboarding[":feature:onboarding"]
briefing[":feature:briefing"]
widgets[":feature:widgets"]
end
playback[":core:playback"]
catalog[":core:catalog"]
downloads[":core:downloads"]
ranking[":core:ranking"]
rss[":core:rss"]
analytics[":core:analytics"]
prefs[":core:prefs"]
domain[":core:domain"]
database[":core:database"]
network[":core:network"]
auth[":core:auth"]
design[":core:designsystem"]
model[":core:model"]
app --> features
app --> playback
app --> catalog
app --> downloads
app --> design
features --> playback
features --> catalog
features --> downloads
features --> design
features --> analytics
features --> ranking
features --> model
playback --> catalog
playback --> downloads
playback --> ranking
playback --> analytics
downloads --> catalog
catalog --> rss
catalog --> prefs
catalog --> domain
catalog --> database
catalog --> ranking
catalog --> network
catalog --> model
rss --> database
rss --> domain
ranking --> database
ranking --> domain
ranking --> prefs
database --> network
domain --> network
network --> auth
rss --> model
ranking --> model
analytics --> model
prefs --> model
domain --> model
database --> model
network --> model
auth --> model
design --> analytics
design --> model
Not every feature uses every edge shown for the group; each module's build.gradle.kts has the exact list. The main runtime stack is playback → catalog → prefs / domain / database / network / model.
Checked by Konsist in :core:testing:
- Features do not depend on, or import from, other features.
-
:core:playbackdepends on:core:catalog; catalog never depends on playback. -
:core:catalogdoes not depend on:core:designsystem. -
:core:catalogdoes not expose:core:analyticsor:core:rankingthroughapi(); ranking isimplementationonly. - No Hilt, Koin, Dagger or MockK in production sources or Gradle test dependencies.
Checked by a CI script:
- Feature sources never import PostHog directly (
scripts/ci/check-feature-no-posthog.sh). Analytics go through:core:analytics.
Kept by convention (documented, not enforced in CI):
- Kotlin files under
*/src/main/**stay under 1,000 lines. Larger units are split into same-package helpers.
AppContainer owns the shared object graph and is created only in BoxLoreApplication. MainActivity, the navigation host, workers and services read it; none of them build their own repositories.
┌────────────────────────────────────────┐
│ BoxLoreApplication │
│ └── container = AppContainer(this) │
└───────────────────┬────────────────────┘
│ reads the container
┌──────────────┴──────────────┐
▼ ▼
MainActivity / NavHost WorkerFactory / Services
-
Construction order: database →
PodcastRepository→QueueRepository→PlaybackRepository→QueueManager→SmartDownloadManager, with RSS and ranking peers created alongside the database. -
ViewModel assemblers: Home, Settings and Info ViewModels are built by
HomeViewModelAssembler,SettingsViewModelAssemblerandInfoViewModelAssembler. -
Ports for tests:
:core:domaindefines narrow ports such asLocalEpisodeCatalogPortandEpisodeSupplementPort, so ViewModels and workers can take fakes from:core:testingin fast JVM tests without a real database.
Playback runs in BoxLorePlaybackService, a Media3 MediaLibraryService.
-
One playback repository
Exactly one UI-scoped
PlaybackRepositoryexists. Navigation routes and workers must never create a second one. -
Smart Queue refill
Only
BoxLorePlaybackServicerefills the queue when it runs low. See Features for what it adds. -
Android Auto
The service's browse tree (
AutoBrowseContract) offers Home (continue listening, queue, new episodes, a drive mix), Library (subscriptions, liked, history), Downloads and Discover.AutoPlaybackResumptionHandlerreads the saved player state so playback can resume after a reconnect. - Google Cast Cast support hands playback to a Cast receiver and keeps the in-app player in sync.
boxlore was once called boxcast. These names are part of the shipping app; renaming them breaks upgrades, WorkManager, Media3 or deep links, so they stay.
| Area | Contract | Bridge |
|---|---|---|
| Application ID | cx.aswin.boxlore |
Never changes |
| Packages | cx.aswin.boxlore.* |
Standard root namespace |
| Main Room database | boxlore_database |
Renamed from legacy boxcast_database on startup |
| Ranking Room database | adaptive_ranking_database |
Owned by :core:ranking only |
| DataStore | user_preferences |
Preferences DataStore |
| SharedPreferences | boxlore_* |
PrefsFileMigrator copies legacy boxcast_* files; key strings stay stable |
| Legacy workers |
cx.aswin.boxcast.core.data.* and cx.aswin.boxlore.core.data.*
|
LegacyWorkerFactory maps them to :core:downloads workers |
| Legacy services | cx.aswin.boxlore.core.data.service.* |
Permanent stub classes kept by R8; the manifest registers the core.playback.service classes |
| Deep links |
boxlore:// and boxcast://
|
Both schemes stay, plus the HTTPS share paths |
| Episode IDs |
rss: prefix and negative IDs |
Stable identity for direct RSS shows and episode supplements |
- Features overview
- Recommendations & discovery
- Playback
- Podcasting 2.0
- Folders, tags & icons
- Smart Queue & downloads
- Make it yours
- Backup, sync & import
boxlore · Curious. Calm. Yours.