Skip to main content

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 on StateFlow for UI state management.

Naming Conventions

ElementConventionExample
ClassesPascalCaseScrollDataRepository, MainScreen
FunctionscamelCasegetUsageSnapshot(), onForegroundAppChanged()
ConstantsSCREAMING_SNAKEKEY_LAST_BACKUP_DATE, EPSILON_MS
Packageslowercasein.scrollless.android.data
FilesPascalCaseLimitMonitor.kt

Component Naming

TypePatternExample
Screens{Feature}ScreenInsightsScreen, SettingsScreen
ViewModels{Feature}ViewModelInsightsViewModel
Repositories{Domain}RepositoryLimitsRepository
Implementations{Interface}ImplLimitsRepositoryImpl
DAOs{Entity}DaoScrollSessionDao
Workers{Action}WorkerDailyProcessingWorker
Routes{Feature}RouteAppDetailRoute (inside ScreenRoutes)

Package Structure

in.scrollless.android/
├── di/                 # Hilt modules (Provides/Binds)
├── ui/
│   ├── theme/          # Color, Type, Shapes
│   ├── components/     # Shared composables (AppCard, AppLoader)
│   └── {feature}/      # Feature specific UI (Screen + ViewModel)
├── data/
│   ├── remote/         # API definitions
│   └── {Repository}    # Repository Interfaces & Impls
├── db/                 # Room Database, Entities, DAOs
├── services/           # Android Services (Foreground) & Logic Managers
├── workers/            # WorkManager definitions
└── util/               # Extension functions and helpers

Code Patterns

Screen & ViewModel Pattern

The UI layer follows a strict Unidirectional Data Flow (UDF) pattern.
1

State Definition

Define a data class XUiState to hold all data required by the screen.
2

State Production

In the ViewModel, use combine on multiple flows (Repositories, Preferences) to produce a single StateFlow<XUiState>.
3

State Consumption

In the Composable, observe state using collectAsStateWithLifecycle() or collectAsState().

Repository Pattern

Data access is abstracted behind interfaces to allow for easy testing and swapping of implementations.
1

Interface

Define the contract in data/ (e.g., LimitsRepository).
2

Implementation

Implement the logic in data/ with Impl suffix (e.g., LimitsRepositoryImpl). Inject DAOs and DataSources here.
3

Binding

Bind the implementation to the interface in di/AppSingletonBinds.kt using @Binds.

Service & Logic Pattern

Complex background logic (like tracking usage) is split into “Monitors” or “Engines” rather than stuffing everything into the Android Service class.
1

Service Shell

ScrollTrackService acts as the entry point and holds the lifecycle.
2

Logic Classes

Logic is delegated to injected singletons like LimitMonitor, NudgeMonitor, or UsageProjectionEngine.
3

Scope Management

Use @ApplicationScope for coroutines that must survive the service lifecycle or screen rotation.

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

ElementStyle
Public APIsKDoc is optional but encouraged for complex logic.
Complex logicInline comments explaining why, not what.
TODOs// TODO: description or // BUG FIX: markers.
Deprecation@Deprecated with ReplaceWith where possible.

Rules

  1. Reactive UI: The UI should react to state changes. Avoid manually updating views; update the underlying data/flow instead.
  2. Offline First: Repositories should generally return data from the local Database (Flow), triggering network/system syncs in the background.
  3. Strict Typing: Use sealed classes for UI States (Loading, Success, Error) and Navigation Routes.
  4. Scoped Dispatchers: Do not hardcode Dispatchers.IO. Inject @IoDispatcher or @MainDispatcher to facilitate testing.
  5. Start output directly with frontmatter - no wrapping in code blocks.
Last modified on January 25, 2026