:core:analytics
July 26, 2026 · View on GitHub
Purpose
Owns analytics event capture and non-fatal error reporting for Boxlore. The module wraps PostHog behind application interfaces so callers do not depend on the SDK directly. It does not initialize PostHog, own UI, own repositories, or persist data beyond analytics first-launch preferences.
Public API
Analyticsis the interface used by production code and tests.AnalyticsHelperis the PostHog-backed production singleton.RecordingAnalyticsis an in-memory test double.ErrorReporteris the non-fatal error facade;:appmay install Crashlytics-backed reporting.PendingEntryPointbridges playback entry-point context across MediaController boundaries.PlayerSessionAggregatorbatches per-episode player interaction events.- Event names and property expectations are documented in
docs/ANALYTICS_EVENT_GLOSSARY.md. AnalyticsGlossary.normalizeEntryPointmaps legacy/free-form strings onto the glossary sheet without renaming dashboard events (learn_history→learn;briefingstaysbriefing).- Glossary façades include growth/session (
trackOnboardingAbandoned,trackSessionRestorePrompt), library ops (trackDownloadRequested,trackShareContent,trackBackupRestoreResult,trackQueueModified), discovery (trackExploreSearchPerformedwithsearch_mode,trackSearchResultTapped), and onboarding (trackOnboardingStepViewedwith optionalstep_index).
Internal structure
src/main/java/cx/aswin/boxlore/core/analytics/
Analytics.kt
AnalyticsHelper.kt # PostHog singleton + track façades
AnalyticsEmit.kt
*AnalyticsTracks.kt # domain emit implementations
ErrorReporter.kt
PendingEntryPoint.kt
RecordingAnalytics.kt
SessionAggregator.kt
Dependencies
- Project dependencies:
:core:model,:core:prefs. - Libraries: PostHog Android SDK and AndroidX core.
- Reverse-edge rule: analytics must not depend on catalog, database, network, playback, downloads, designsystem, or feature modules. Callers that emit events declare this module directly.
Threading / lifecycle
AnalyticsHelperis process-scoped and its methods are callable from any thread; the SDK handles internal dispatch.PendingEntryPointandPlayerSessionAggregatoruse synchronized or volatile state for cross-thread access.RecordingAnalytics.flush()is a no-op suitable for tests.
Persistence & identity
- SharedPreferences file
boxlore_analytics_prefsstores the first-launch flag and migrates fromboxcast_analytics_prefsthroughPrefsFileMigrator. - Preference key
is_first_launchmust remain stable. - Event identity must remain aligned with
docs/ANALYTICS_EVENT_GLOSSARY.md.
Lifecycle / volume (no double-count)
- Opens / backgrounds / install counts come from PostHog SDK autocapture (
Application Opened,Application Backgrounded,Application Installed) withcaptureApplicationLifecycleEvents = true. - Do not emit glossary
app_open,app_background, or volumeinstall_attributed. - Install channel:
$set_oncepersoninstall_channelonly (trackInstallChannelAttributed). - Deep links: custom
deep_link_opened(captureDeepLinks = false). - Weekly Pulse KPIs stay on Application Opened / Application Installed.
- Coverage inventory:
docs/analytics/glossary_emission_coverage.csv(emission|sdk_backed|person_props_only).
Testing notes
- Unit tests live under
src/test/java/cx/aswin/boxlore/core/analytics. - Use
RecordingAnalyticswhen testing classes that acceptAnalytics. GlossaryAllEventsEmissionTestcovers everyemission:inventory row;LifecycleSdkMappingTest/InstallChannelAttributionTestassert no dual open/install emits.- Architecture guards in
:core:testingenforce CSV ↔ allowlist ↔ inventory parity.
./gradlew :core:analytics:testDebugUnitTest
./gradlew :core:testing:testDebugUnitTest --tests '*GlossaryCoverageGuardTest'
CI relevance
unit-tests.ymlruns the module JVM tests.- The root Kover merged verification includes this module.
scripts/ci/check-feature-no-posthog.shprevents feature modules from importing or calling PostHog directly.