Summary
The codebase follows modern Android development practices, utilizing Jetpack Compose for UI, Hilt for dependency injection, and Coroutines/Flow for asynchronous operations. It adheres to Clean Architecture principles, separating concerns into UI, Data (Repositories), and Infrastructure (Services/Workers) layers. The style is functional and reactive, heavily relying onStateFlow for UI state management.
Naming Conventions
| Element | Convention | Example |
|---|---|---|
| Classes | PascalCase | ScrollDataRepository, MainScreen |
| Functions | camelCase | getUsageSnapshot(), onForegroundAppChanged() |
| Constants | SCREAMING_SNAKE | KEY_LAST_BACKUP_DATE, EPSILON_MS |
| Packages | lowercase | in.scrollless.android.data |
| Files | PascalCase | LimitMonitor.kt |
Component Naming
| Type | Pattern | Example |
|---|---|---|
| Screens | {Feature}Screen | InsightsScreen, SettingsScreen |
| ViewModels | {Feature}ViewModel | InsightsViewModel |
| Repositories | {Domain}Repository | LimitsRepository |
| Implementations | {Interface}Impl | LimitsRepositoryImpl |
| DAOs | {Entity}Dao | ScrollSessionDao |
| Workers | {Action}Worker | DailyProcessingWorker |
| Routes | {Feature}Route | AppDetailRoute (inside ScreenRoutes) |
Package Structure
Code Patterns
Screen & ViewModel Pattern
The UI layer follows a strict Unidirectional Data Flow (UDF) pattern.State Production
In the ViewModel, use
combine on multiple flows (Repositories, Preferences) to produce a single StateFlow<XUiState>.Repository Pattern
Data access is abstracted behind interfaces to allow for easy testing and swapping of implementations.Implementation
Implement the logic in
data/ with Impl suffix (e.g., LimitsRepositoryImpl). Inject DAOs and DataSources here.Service & Logic Pattern
Complex background logic (like tracking usage) is split into “Monitors” or “Engines” rather than stuffing everything into the Android Service class.Logic Classes
Logic is delegated to injected singletons like
LimitMonitor, NudgeMonitor, or UsageProjectionEngine.Common Idioms
Logging
Use
Timber for all logging.
Timber.tag(TAG).d("Message") or Timber.e(e, "Error").Coroutines
Use
viewModelScope in ViewModels. Use injected externalScope (Application Scope) in Repositories/Services for fire-and-forget tasks.Dependency Injection
Prefer Constructor Injection (
@Inject constructor(...)). Use Field Injection only for Android components (Activities, Services).Navigation
Use the
ScreenRoutes sealed class to define routes and arguments. Do not hardcode route strings in navigate() calls.Documentation Standards
| Element | Style |
|---|---|
| Public APIs | KDoc is optional but encouraged for complex logic. |
| Complex logic | Inline comments explaining why, not what. |
| TODOs | // TODO: description or // BUG FIX: markers. |
| Deprecation | @Deprecated with ReplaceWith where possible. |
Rules
- Reactive UI: The UI should react to state changes. Avoid manually updating views; update the underlying data/flow instead.
- Offline First: Repositories should generally return data from the local Database (
Flow), triggering network/system syncs in the background. - Strict Typing: Use sealed classes for UI States (Loading, Success, Error) and Navigation Routes.
- Scoped Dispatchers: Do not hardcode
Dispatchers.IO. Inject@IoDispatcheror@MainDispatcherto facilitate testing. - Start output directly with frontmatter - no wrapping in code blocks.