Local Storage: Room DB & DAOs
Relevant source files
The following files were used as context for generating this wiki page:
- app/src/main/kotlin/com/fathiraz/flowbell/data/local/database/FlowBellDatabase.kt
- app/src/main/kotlin/com/fathiraz/flowbell/data/local/database/dao/NotificationQueueDao.kt
- app/src/main/kotlin/com/fathiraz/flowbell/data/local/database/dao/UserPreferencesDao.kt
- app/src/main/kotlin/com/fathiraz/flowbell/data/repositories/UserPreferencesRoomRepository.kt
- app/src/main/kotlin/com/fathiraz/flowbell/core/di/DataModule.kt
- app/src/main/kotlin/com/fathiraz/flowbell/data/local/database/repository/AppPreferencesRepository.kt
Introduction
The local storage layer in FlowBell is built upon the Room persistence library, providing an abstraction over SQLite for robust on-device data management. Its primary purpose is to manage notification queues, user preferences, and per-app configurations entirely offline, ensuring that the bridge between device notifications and webhooks remains reliable even during network intermittent connectivity.
The system is architected to handle high-frequency notification events by utilizing a queue-based approach. It stores metadata for every captured notification, tracks delivery status (Pending, Sent, or Failed), and maintains user settings such as webhook URLs and filtering keywords. This layer serves as the "Source of Truth" for the application's ViewModels and background services.
Sources: README.md, app/src/main/kotlin/com/fathiraz/flowbell/data/local/database/FlowBellDatabase.kt:1-25
Core Database Architecture
The FlowBellDatabase class serves as the main access point for the persisted data. It is configured with three primary entities and managed as a singleton via Koin Dependency Injection.
Database Entities
The schema is divided into three functional areas:
- Notification Queue: Stores the content of notifications, their status, and HTTP response details.
- App Preferences: Manages per-package settings, such as whether forwarding is enabled and specific keyword filters.
- User Preferences: Global settings including the webhook URL, theme mode, and onboarding status.
Sources: app/src/main/kotlin/com/fathiraz/flowbell/data/local/database/FlowBellDatabase.kt:20-25, app/src/main/kotlin/com/fathiraz/flowbell/core/di/DataModule.kt:28-33
Schema Evolution and Migrations
The database is currently at Version 10. The evolution of the schema reflects the addition of complex features such as HTTP logging, performance indexing, and granular notification filtering.
The diagram above illustrates the migration path of the FlowBell Room database schema.
Sources: app/src/main/kotlin/com/fathiraz/flowbell/data/local/database/FlowBellDatabase.kt:21-170
Data Access Objects (DAOs)
DAOs define the interactions with the database using Coroutines for asynchronous operations and Flows for reactive UI updates.
NotificationQueueDao
This DAO manages the lifecycle of a notification from capture to delivery. It includes specialized queries for batch processing and cleanup.
| Function | Purpose | Return Type |
|---|---|---|
getPendingNotifications | Retrieves notifications for batch delivery. | List<NotificationQueue> |
getRetryableNotifications | Finds failed sends that haven't hit max retries. | List<NotificationQueue> |
getQueueStatsFlow | Provides real-time counts of Sent/Failed/Pending. | Flow<List<QueueStatusCount>> |
cleanupOldSentNotifications | Deletes historical data based on a cutoff time. | Unit |
Sources: app/src/main/kotlin/com/fathiraz/flowbell/data/local/database/dao/NotificationQueueDao.kt:15-100
UserPreferencesDao
Specifically hardened for device compatibility (e.g., Samsung eMMC issues), this DAO ensures a single configuration row (ID=1) always exists.
@Transaction
suspend fun ensurePreferencesExist() {
if (preferencesExist() == 0) {
insertOrUpdatePreferences(UserPreferencesEntity())
}
}
Sources: app/src/main/kotlin/com/fathiraz/flowbell/data/local/database/dao/UserPreferencesDao.kt:89-94
Data Flow: Notification Lifecycle
The following sequence shows how the storage layer interacts with the system when a notification is processed.
Data flow from initial capture to final delivery status update in the local DB.
Sources: app/src/main/kotlin/com/fathiraz/flowbell/data/local/database/dao/NotificationQueueDao.kt:21-65, README.md
Implementation Details
Type Converters
To support complex Kotlin types in SQLite, the database utilizes TypeConverters:
- NotificationQueueStatusConverter: Maps the
Statusenum to Strings. - StringListConverter: Converts
List<String>(used for keyword filters) into comma-separated strings for storage.
Sources: app/src/main/kotlin/com/fathiraz/flowbell/data/local/database/FlowBellDatabase.kt:24-25
Performance Optimization
Version 7 of the database introduced indices to speed up dashboard and history queries:
idx_created_atonnotification_queue(createdAt)idx_statusonnotification_queue(status)idx_app_nameandidx_package_namefor filtering.
Sources: app/src/main/kotlin/com/fathiraz/flowbell/data/local/database/FlowBellDatabase.kt:117-128
Dependency Injection Integration
The database and its DAOs are provided via dataModule using Koin.
val dataModule = module {
single { FlowBellDatabase.getDatabase(androidContext()) }
single { get<FlowBellDatabase>().appPreferencesDao() }
single { get<FlowBellDatabase>().notificationQueueDao() }
single { get<FlowBellDatabase>().userPreferencesDao() }
}
Sources: app/src/main/kotlin/com/fathiraz/flowbell/core/di/DataModule.kt:28-33
Conclusion
The local storage system in FlowBell is a critical component that ensures data persistence and operational visibility. By combining Room's structured entities with reactive DAOs and a robust migration strategy, the application maintains a reliable history of notification events and user configurations, allowing for advanced features like batch delivery and analytics without the need for an external backend database.