Skip to main content

Summary

The data layer is the foundation of the application, designed to be offline-first. It captures high-frequency user interactions (like scrolling, unlocking, and app usage) and stores them locally on the device. To ensure the app remains fast, this layer automatically transforms raw, heavy data logs into lightweight daily summaries and timelines that the UI can display instantly.

Data Flow

The application uses a “write-heavy, read-aggregated” architecture. Raw events flow in from system services, are stored immediately, and are later processed into optimized tables for the UI.
1

Data Ingestion

The app listens to Android system events (accessibility, usage stats, notifications) and writes them directly to Raw Event Tables (e.g., raw_app_events, scroll_sessions).
2

Aggregation

Because raw tables grow very large, the app calculates totals (like “Total time on Instagram today”) and saves them into Summary Tables (e.g., daily_app_usage).
3

UI Consumption

The screens observe these Summary Tables. When the UI needs a timeline, it reads from the User Journey table, which is a pre-built view of the day’s events optimized for scrolling performance.

Data Modules

The database is split into logical modules based on the type of data they handle.

Usage & Events

What it manages: Tracking how the user interacts with their device. Key Entities:
EntityPurpose
RawAppEventThe lowest level log. Records screen on/off, app opens, and service interactions.
DailyAppUsageRecordA summary of how long each app was used per day.
DailyDeviceSummaryHigh-level daily stats: total unlocks, total notification count, and total screen time.
UnlockSessionRecordTracks every time the phone is unlocked, how long it stayed unlocked, and why it ended.

Scrolling & Physics

What it manages: Measuring the physical distance the user scrolls. Key Entities:
EntityPurpose
ScrollSessionRecordRecords a specific bout of scrolling in an app, measuring distance in pixels or micrometers.

Limits & Interventions

What it manages: User-defined rules to block apps and the history of those interventions. Key Entities:
EntityPurpose
LimitGroupA collection of apps (e.g., “Social Media”) that share a time budget.
LimitedAppLinks a specific app package to a Limit Group.
LimitOutcomeEntityTracks if a user succeeded or failed their limits for a specific day.
SnoozeHistoryEntityRecords when a user “snoozes” a block, used to calculate exponential cooldowns.

Notifications

What it manages: Logging incoming notifications to analyze distraction patterns. Key Entities:
EntityPurpose
NotificationRecordStores details about every notification: the app, time, and whether it was clicked or dismissed.

App Metadata

What it manages: Information about the apps installed on the device. Key Entities:
EntityPurpose
AppMetadataCaches app names, versions, and installation status so the app doesn’t have to query the OS constantly.
AppCategoryStores the category of an app (e.g., “Productivity”, “Social”) and its sync status with the remote API.

Specialized Data Structures

The User Journey (Materialized View)

The UserJourney table is unique. It acts as a .
  • Problem: Constructing a timeline of a user’s day requires joining Unlock Sessions, App Events, and Notifications. Doing this live makes the UI laggy.
  • Solution: The app processes these raw tables in the background and writes a simplified, chronological list of events into the user_journey table.
  • Result: The UI simply reads this table row-by-row to render the timeline instantly.

Nudge State Store (Preferences)

Not all data lives in the database. Transient state is stored in SharedPreferences via the NudgeStateStore.
KeyPurpose
last_any_nudge_msTimestamp of the last time any intervention was shown (to prevent spamming).
session_active_pkgWhich app is currently being tracked in the active session.

Caching & Synchronization Strategy

The app employs a strict Local-First strategy with specific rules for data retention and aggregation.
StrategyDescription
Aggressive AggregationRaw events are heavy. The app calculates daily totals (DailyAppUsageRecord) continuously so it doesn’t have to sum up millions of raw rows every time the user opens the dashboard.
MaterializationComplex views (like the daily timeline) are pre-calculated and stored in UserJourney. If the raw data changes, this table is regenerated.
Data PruningTo save storage space, raw high-frequency data (like RawAppEvent) can be deleted after a certain period, while the aggregated summaries (DailyDeviceSummary) are kept indefinitely for long-term statistics.
Category SyncAppCategory uses a status flag (NEEDS_CHECK, SYNCED, FAILED) to manage synchronization with a remote API without blocking the UI.
Last modified on January 25, 2026