Skip to content

Architecture

Aswin C edited this page Sep 9, 2026 · 2 revisions

System Architecture

boxlore is designed as a modular, multi-module Android project following clean architectural boundaries, unidirectional data flow (UDF), and strict dependency encapsulation.


Architectural Principles

  1. Inward Dependency Direction
    The application graph is structured so that high-level features depend inward on business logic, catalog repositories, and foundational infrastructure. Lower-level core libraries never depend on higher-level features or UI components.
  2. Feature Isolation
    Features are decoupled horizontally. A feature module is strictly prohibited from declaring Gradle dependencies on, or importing code from, any other feature module. Cross-feature flows are coordinated via the app shell (:app) navigation graph.
  3. Single Composition Root
    The entire runtime object graph is wired through a single, explicit dependency container: AppContainer in :app. No reflection-heavy runtime or compile-time DI frameworks (such as Hilt, Dagger, or Koin) are used.
  4. Architectural Guardrails as Code
    Module isolation rules, dependency directions, and framework prohibitions are continuously validated in CI using Konsist tests in :core:testing.

Module Breakdown

The codebase is organized into three distinct tiers: Shell, Core Infrastructure, and Feature Domains.

boxlore/
├── app/                    # Application shell & Composition Root
├── core/
│   ├── model/             # Shared domain models and enums
│   ├── network/           # Retrofit HTTP client, OkHttp, and network DTOs
│   ├── domain/            # Thin domain ports and decoupled interfaces
│   ├── database/          # Room database (boxlore_database), entities, and DAOs
│   ├── prefs/             # Jetpack DataStore and SharedPreferences abstractions
│   ├── analytics/         # Telemetry and analytics facade (no direct vendor leaks)
│   ├── catalog/           # Catalog orchestration, podcast/folder repositories, backup/restore
│   ├── rss/               # Custom XML parser, direct RSS feed repository, and OPML
│   ├── ranking/           # On-device LinUCB bandit scoring and adaptive Room database
│   ├── downloads/         # Episode download repository and background WorkManager workers
│   ├── playback/          # AndroidX Media3 playback service, queue, and audio session
│   ├── designsystem/      # Material 3 theme, solid surfaces, and reusable UI components
│   └── testing/           # Shared unit test fixtures, fakes, and Konsist architecture guards
└── feature/
    ├── home/              # Dashboard, editorial rails, and settings hub
    ├── player/            # Full player sheet scaffold, scrubber, chapters, transcripts
    ├── info/              # Podcast details, episode lists, and custom tag sheets
    ├── explore/           # Search, semantic concept discovery, and curiosity cards
    ├── library/           # Subscriptions, folders, downloads, history, and backup UI
    ├── onboarding/        # First-run setup, genre selection, and OPML import
    ├── briefing/          # Regional AI news briefing screen
    └── widgets/           # Home-screen RemoteViews widgets (Now Playing, Library, Controls)

Dependency Graph & Enforced Rules

flowchart TB
    app[":app (Shell)"]

    subgraph features ["Feature Tier (Isolated)"]
        home[":feature:home"]
        player[":feature:player"]
        info[":feature:info"]
        explore[":feature:explore"]
        library[":feature:library"]
        onboarding[":feature:onboarding"]
        briefing[":feature:briefing"]
        widgets[":feature:widgets"]
    end

    subgraph domain_playback ["Service & Business Tier"]
        playback[":core:playback"]
        catalog[":core:catalog"]
        downloads[":core:downloads"]
        ranking[":core:ranking"]
    end

    subgraph infra ["Infrastructure & Data Tier"]
        database[":core:database"]
        network[":core:network"]
        rss[":core:rss"]
        prefs[":core:prefs"]
        domain[":core:domain"]
        analytics[":core:analytics"]
        design[":core:designsystem"]
        model[":core:model"]
    end

    app --> features
    app --> domain_playback
    app --> design

    features --> domain_playback
    features --> design
    features --> model
    features --> analytics

    playback --> catalog
    playback --> downloads
    playback --> ranking
    playback --> analytics

    downloads --> catalog

    catalog --> database
    catalog --> network
    catalog --> rss
    catalog --> prefs
    catalog --> domain
    catalog --> ranking
    catalog --> model

    ranking --> database
    ranking --> prefs
    ranking --> model

    rss --> database
    rss --> domain
    rss --> model

    database --> model
    network --> model
    domain --> model
    design --> model
Loading

Invariant Rules (CI Enforced via Konsist)

  • Zero Feature-to-Feature Dependencies: Features must never depend on or import other features.
  • Playback Layering: :core:playback depends on :core:catalog. :core:catalog must never depend on :core:playback.
  • Design Isolation: :core:catalog must never depend on :core:designsystem.
  • Clean Boundaries: :core:catalog must not expose :core:analytics or :core:ranking via api() (strictly implementation).
  • Vendor Decoupling: Feature modules never import PostHog directly. All analytics are routed through :core:analytics.
  • Framework Restrictions: No Hilt, Koin, Dagger, or MockK are permitted anywhere in the repository.
  • Source File Size: Every Kotlin source file under */src/main/** must remain strictly under 1,000 lines.

Dependency Injection: Single Composition Root

Instead of delegating object graph creation to external annotation processors or reflection frameworks, boxlore initializes its runtime graph deterministically inside AppContainer:

┌────────────────────────────────────────┐
│          BoxLoreApplication            │
│  └── appContainer = AppContainer(this) │
└───────────────────┬────────────────────┘
                    │ Reads container
     ┌──────────────┴──────────────┐
     ▼                             ▼
MainActivity / NavHost      WorkerFactory / Services
  • Instantiation Order: Database $\to$ PodcastRepository $\to$ QueueRepository $\to$ PlaybackRepository $\to$ QueueManager $\to$ SmartDownloadManager.
  • ViewModel Construction: Complex ViewModels are constructed via assembler classes (e.g., HomeViewModelAssembler, SettingsViewModelAssembler, InfoViewModelAssembler).
  • Test Isolation via Domain Ports: The :core:domain module defines thin ports (e.g., LocalEpisodeCatalogPort, EpisodeSupplementPort). This enables fast, hermetic JVM unit tests utilizing shared fakes from :core:testing without instantiating database instances or mock engines.

Media3 Playback Architecture

Playback is orchestrated by BoxLorePlaybackService, a foreground AndroidX Media3 MediaLibrarySessionCallback service:

  1. Single Playback Repository:
    Exactly one UI-scoped PlaybackRepository instance exists within the runtime graph. Background workers and separate navigation routes must never instantiate a second playback repository.
  2. Smart Queue Refill:
    Ownership of queue replenishment is strictly held by BoxLorePlaybackService. When the active queue approaches exhaustion, the service automatically draws upcoming episodes from:
    • The active show (same-show continuation).
    • Unfinished episodes from user history.
    • Fresh episodes from subscribed podcasts.
    • On-device recommendations.
  3. Android Auto Resilience:
    The media library session exposes structured browse roots (ROOT_RECENT, ROOT_SUBSCRIPTIONS, ROOT_QUEUE). Disconnection or vehicle shutdown events preserve the active session and mini-player state in permanent storage, enabling seamless playback resumption on reconnect.
  4. External Media Output:
    Integrated Cast support allows streaming media to Google Cast receivers, automatically switching audio dispatch while synchronizing UI playback state.

Upgrade Failsafes & Identity Contracts

To ensure uninterrupted app upgrades across major releases, the following contracts and historical bridges are permanently preserved:

Area Contract Upgrade Bridge / Guard
Application ID cx.aswin.boxlore Immutable shipping identifier
Package Namespaces cx.aswin.boxlore.* Standardized root namespace
Main Room DB boxlore_database Auto-migrated from legacy boxcast_database on startup
Ranking Room DB adaptive_ranking_database Dedicated database owned strictly by :core:ranking
DataStore user_preferences Dedicated Protobuf/Preferences file
SharedPreferences boxlore_* PrefsFileMigrator silently migrates legacy boxcast_* files
Legacy Workers cx.aswin.boxcast.core.data.* LegacyWorkerFactory bridges historical FQCNs to :core:downloads
Legacy Services cx.aswin.boxcast.core.data.service.* Permanent stub classes preserved in AndroidManifest
Deep Link Schemes boxlore:// and boxcast:// Both schemes retained for backwards-compatible link handling
Episode Identifiers rss: prefix & negative IDs Stable hashing contract for custom RSS feeds and supplement items