Data Layer
Relevant source files
The following files were used as context for generating this wiki page:
- app/src/main/java/com/musagenius/ocrapp/data/repository/ScanRepositoryImpl.kt
- app/src/main/java/com/musagenius/ocrapp/data/local/dao/ScanDao.kt
- app/src/main/java/com/musagenius/ocrapp/data/local/entity/ScanEntity.kt
- app/src/main/java/com/musagenius/ocrapp/data/ocr/OCRServiceImpl.kt
- app/src/main/java/com/musagenius/ocrapp/data/mapper/ScanMapper.kt
- app/src/main/java/com/musagenius/ocrapp/data/utils/StorageManager.kt
- README.md
The Data Layer is a core component of the application, responsible for managing all data operations. It abstracts the data sources from the rest of the app, providing a clean API for the Domain Layer to consume. Its primary responsibilities include local data persistence using Room, offline OCR processing via the Tesseract engine, and file system management for images and language data.
This layer follows the principles of Clean Architecture, ensuring that data sources are completely decoupled from the UI and business logic. It consists of repositories, Data Access Objects (DAOs), Room entities, service implementations, and data mappers.
Sources: README.md, ROADMAP.md
Architecture Overview
The Data Layer acts as the single source of truth for the application. The ViewModel (from the Presentation Layer) interacts with Use Cases (Domain Layer), which in turn call Repository interfaces. The ScanRepositoryImpl in the Data Layer implements this interface, coordinating between the local Room database (ScanDao) and other data services like OCRServiceImpl.
The following diagram illustrates the high-level data flow within the application, highlighting the Data Layer's position.
This diagram shows the separation of concerns, with the Data Layer handling all data-related implementation details.
Sources: app/src/main/java/com/musagenius/ocrapp/presentation/viewmodel/OCRViewModel.kt:19-22, app/src/main/java/com/musagenius/ocrapp/data/repository/ScanRepositoryImpl.kt, README.md
Local Persistence (Room)
Local data persistence is handled by the Room persistence library, which provides an abstraction layer over SQLite. It is used to store the metadata and extracted text from every OCR scan.
Sources: app/src/main/java/com/musagenius/ocrapp/data/local/entity/ScanEntity.kt, app/src/main/java/com/musagenius/ocrapp/data/local/dao/ScanDao.kt
ScanEntity
The ScanEntity class defines the schema for the scans table in the database. It includes all the necessary fields to represent a completed scan, with indices on timestamp and language to optimize sorting and filtering operations.
// Source: app/src/main/java/com/musagenius/ocrapp/data/local/entity/ScanEntity.kt:13-48
@Entity(
tableName = "scans",
indices = [
Index(value = ["timestamp"]),
Index(value = ["language"])
]
)
data class ScanEntity(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id")
val id: Long = 0,
// ... other fields
)
The following ER diagram visualizes the scans table structure.
ScanDao
The ScanDao (Data Access Object) is an interface that defines all database operations. Room generates the implementation at compile time. It provides a comprehensive set of methods for CRUD (Create, Read, Update, Delete) operations, as well as complex queries for searching, filtering, and retrieving scans. Many query methods return a Flow<List<ScanEntity>>, allowing the UI to reactively update when the underlying data changes.
Key methods in ScanDao:
| Method Signature | Description |
|---|---|
insert(scan: ScanEntity): Long | Inserts a single scan and returns its new ID. |
getAllScans(): Flow<List<ScanEntity>> | Retrieves all scans, ordered by newest first, as a reactive flow. |
getById(scanId: Long): ScanEntity? | Fetches a single scan by its primary key. |
searchScans(query: String): Flow<List<ScanEntity>> | Performs a full-text search across extracted_text, title, tags, and notes. |
deleteById(scanId: Long) | Deletes a scan by its ID. |
updateFavoriteStatus(scanId: Long, isFavorite: Boolean) | Updates the favorite status of a specific scan. |
deleteOldScans(cutoffDate: Long): Int | Deletes scans older than a given timestamp and returns the count of deleted rows. |
getHighConfidenceScans(threshold: Float): Flow<List<ScanEntity>> | Retrieves scans with a confidence score above a certain threshold. |
Sources: app/src/main/java/com/musagenius/ocrapp/data/local/dao/ScanDao.kt
Repository Pattern
The ScanRepositoryImpl class implements the ScanRepository interface from the domain layer. It serves as a mediator between the domain layer and the data sources, abstracting the origin of the data.
Responsibilities
- Data Abstraction: Exposes data to the rest of the app without revealing the implementation details (e.g., whether data comes from a database or a remote server).
- Data Caching and Synchronization: (Not yet implemented, but is a repository responsibility).
- Error Handling: Wraps data operations in a
Resultsealed class, providing a standardized way to handle success and failure states.
The following sequence diagram shows the process of inserting a new scan.
This flow demonstrates how the repository converts the domain model (ScanResult) to a database entity (ScanEntity) before persisting it via the DAO.
Sources: app/src/main/java/com/musagenius/ocrapp/data/repository/ScanRepositoryImpl.kt:45-57, app/src/main/java/com/musagenius/ocrapp/data/mapper/ScanMapper.kt:43-62
Data Mapping
To maintain a clean separation between the data and domain layers, a mapper is used to convert data structures. ScanMapper.kt contains extension functions to map between the ScanEntity (data layer model) and ScanResult (domain layer model).
ScanEntity.toDomain(): Converts a database entity to a domain model.ScanResult.toEntity(): Converts a domain model to a database entity.
A key transformation is handling the tags field, which is stored as a comma-separated String in ScanEntity but is represented as a List<String> in ScanResult for easier use in the domain and presentation layers.
This diagram shows the relationship between the two models and the mapper that converts between them.
Sources: app/src/main/java/com/musagenius/ocrapp/data/mapper/ScanMapper.kt:13-62, app/src/test/java/com/musagenius/ocrapp/data/mapper/ScanMapperTest.kt
OCR Service
The OCRServiceImpl is a singleton service that encapsulates all logic related to Tesseract OCR processing. It is designed to be thread-safe and manages the lifecycle of the TessBaseAPI.
Key Features
- Tesseract Wrapper: Provides a clean Kotlin interface over the native
TessBaseAPI. - Thread Safety: Uses a
Mutexto serialize all access to the Tesseract instance, preventing race conditions from concurrent OCR requests. - Initialization and Language Management: Handles the initialization of Tesseract and manages
tessdatafiles. On first use for a specific language, it copies the required.traineddatafile from the app's assets to the device storage. - Image Preprocessing: Integrates with an
ImagePreprocessorto enhance images before recognition, improving accuracy. - Progress Tracking: The
recognizeTextWithProgressmethod returns aFlowthat emitsOCRProgressupdates, allowing the UI to display a detailed progress bar to the user.
The sequence diagram below outlines the flow for recognizing text with progress updates.
Storage Management
The StorageManager class is a utility responsible for all file system interactions beyond the Room database.
Responsibilities
- Storage Checks: Provides methods like
hasAvailableStorage()to ensure there is enough space on the device before performing operations like capturing an image or downloading a language file. - Usage Calculation: The
calculateAppStorageUsage()method calculates the space consumed by app-generated files (e.g., images, cache). - File Cleanup:
cleanupOldFiles()can be used to delete temporary or old files to free up space. - Cache Management:
clearCache()provides a way to delete all files in the app's cache directory.
This class centralizes storage logic, making it easier to manage files and prevent the app from consuming excessive disk space.
Sources: app/src/main/java/com/musagenius/ocrapp/data/utils/StorageManager.kt
Summary
The Data Layer of the OCR Android App is a well-structured and robust component that effectively decouples data handling from the rest of the application. It leverages modern Android libraries like Room for persistence and Kotlin Coroutines for asynchronous operations. Key components like ScanRepositoryImpl, ScanDao, OCRServiceImpl, and StorageManager work together to provide a comprehensive and efficient data management solution, serving as the foundation for all application features.