Summary
The application follows a Clean Architecture approach using MVVM (Model-View-ViewModel) with Jetpack Compose for the UI. It relies heavily on a “Local-First” data strategy where the Room database is the single source of truth for the UI, while background services and workers synchronize data from Android system APIs (Accessibility, UsageStats) into that database. The codebase uses Hilt for dependency injection and Coroutines/Flow for asynchronous operations.
Show Key Implementation Files
Entry Points : MainActivity.kt, ScrollTrackApplication.kt
Core Services : ScrollTrackService.kt (Accessibility), LimitMonitor.kt
Data Layer : ScrollDataRepositoryImpl.kt, AppDatabase.kt
Logic Engines : UsageProjectionEngine.kt, DailyDataProcessor.kt
UI : AppDetailScreen.kt, NavGraph.kt
Feature Structure
Most features (screens) are loosely grouped by domain but physically located in ui/ packages. A typical feature consists of:
Screen (Composable) : Accepts a ViewModel and Navigation Controller.
ViewModel : Holds StateFlow for UI state, interacts with Repositories.
Repository : Fetches data from DB or System.
Screen Implementation Pattern
Screens typically collect state using collectAsStateWithLifecycle() and pass events back to the ViewModel.
ViewModel Pattern
ViewModels use StateFlow to expose UI state. They often combine multiple data streams (e.g., Usage Data + App Icons + Filter Settings) into a single UI State object.
Conventions:
Hilt Injection : All ViewModels are annotated with @HiltViewModel.
Flow Combination : Heavy use of combine to merge database updates with user filters (e.g., filterSet in ScrollDataRepositoryImpl).
Coroutines : viewModelScope is used for launching suspend functions.
Core Architecture: The Tracking Pipeline
The app’s core function—tracking usage and scrolling—uses a specific pipeline to move data from the Android System to the UI.
1. Data Ingestion (The Write Path)
Data enters the system through two primary channels:
Real-time (Accessibility) : ScrollTrackService captures scroll events and window changes. It writes raw events directly to raw_app_events via RawAppEventDao.
Historical/Backup (UsageStats) : UsageStatsWorker polls Android’s UsageStatsManager to fill in gaps or handle apps where accessibility is disabled.
2. Data Processing (The Aggregation Path)
Raw events are too heavy for direct UI consumption. The app uses a “Process and Summarize” strategy.
DailyDataProcessor : A logic class that takes raw events and calculates:
Total Screen Time
Unlock Counts
Scroll Distance (Pixels -> Meters conversion)
Trigger : This runs via DailyProcessingWorker (periodic) or ScrollDataRepository.processAndSummarizeDate (on app open).
Output : Writes to summary tables (daily_app_usage, daily_device_summary).
3. Real-Time Limit Enforcement
To block apps instantly, the app cannot wait for database writes. It uses a Usage Projection Engine .
Formula:
Total Usage = Persisted DB Usage + Pending Buffer + Live Session Duration
Persisted : Data already saved in daily_app_usage.
Buffer : Sessions that finished in the last few seconds but aren’t in the DB yet.
Live : ActiveSessionTracker measures the current foreground session using MonotonicClock.
Dependency Injection (Hilt)
The app uses Hilt for DI. The configuration is centralized in di/.
Module File Purpose AppSingletonProvides AppSingletonProvides.ktProvides global singletons: AppDatabase, DAOs, Retrofit, WorkManager, Clock. AppSingletonBinds AppSingletonProvides.ktBinds interface implementations (e.g., LimitsRepositoryImpl to LimitsRepository). ViewModelModules ViewModelModules.ktProvides ViewModel-scoped delegates (e.g., LimitViewModelDelegate).
Scopes Used:
@Singleton: Used for Repositories, Database, and “Monitor” classes (LimitMonitor, NudgeMonitor) that must maintain state across the app lifecycle.
@ApplicationScope: A custom qualifier for a CoroutineScope that survives UI destruction (used for fire-and-forget DB writes).
Navigation
Navigation is handled by Jetpack Navigation Compose in NavGraph.kt.
Routes : Defined as a sealed class ScreenRoutes.
Arguments : Passed via route strings (e.g., app_detail/{packageName}).
Graph Structure :
DASHBOARD_GRAPH: Main tabs (Dashboard, Usage, Unlocks).
SETTINGS_GRAPH: Settings, About, FAQ.
LIMITS_GRAPH: Limit management.
CREATE_LIMIT_FLOW: A nested graph for the multi-step limit creation wizard.
Background Work
The app employs a hybrid approach for background tasks:
Component Type Use Case Implementation ScrollTrackService Accessibility Service Real-time scrolling, app switching detection, overlay drawing. ScrollTrackService.ktAppTrackerService Foreground Service Fallback usage tracking if Accessibility is disabled. AppTrackerService.ktLimitEnforcementWorker Periodic Worker Safety net to re-check limits every 15 mins. LimitEnforcementWorker.ktDailyProcessingWorker Periodic Worker Aggregates raw data into summaries, runs nightly. DailyProcessingWorker.ktAppMetadataSyncWorker OneTime Worker Fetches icons/labels when a new app is installed. AppMetadataSyncWorker.kt
Data Layer Conventions
Repository Pattern
Repositories act as the gatekeepers. They expose Flow<T> for data that changes (UI) and suspend functions for one-off operations.
Naming : [Feature]Repository (Interface) -> [Feature]RepositoryImpl (Implementation).
Source of Truth : Almost always the local Database. Network calls (like AppCategoryRepository) fetch data and immediately write to the DB; the UI observes the DB, not the network.
Database (Room)
Entities : Located in db/.
DAOs : Provide Flow return types for reactive updates.
Transactions : appDatabase.withTransaction { ... } is used heavily in ScrollDataRepositoryImpl to ensure data consistency when summarizing daily stats.
Common Utilities
Class Purpose Location DateUtil Standardizes timestamp/date string conversions. util/DateUtil.ktPermissionUtils Checks/Requests Android permissions (Usage, Overlay). util/PermissionUtils.ktScrollPhysics Converts pixels to physical distance (meters). util/ScrollPhysics.ktMonotonicClock Provides time that doesn’t jump (for session tracking). util/MonotonicClock.kt
Last modified on January 25, 2026