Database Schema and Repository Pattern in SmsForwarder: Room Architecture Explained
SmsForwarder uses Android Room persistence library with version 21 schema, implementing six core entities and a read-only view wrapped in thin repository classes that abstract DAO operations for the ViewModel layer.
The open-source Android application SmsForwarder (available at pppscn/SmsForwarder) manages SMS forwarding rules and logs using a structured SQLite database accessed through Room. The architecture follows the repository pattern to separate data access logic from UI components, ensuring testable and maintainable code. This guide examines the entity definitions in AppDatabase, the migration chain from version 1 to 21, and how repository classes encapsulate Data Access Objects (DAOs).
Core Database Entities
The schema defines six primary entities and one database view, each mapped to Kotlin data classes annotated with Room annotations.
Primary Entities
-
Rule– Stores forwarding rules with filtering logic. Located inapp/src/main/kotlin/cn/ppps/forwarder/database/entity/Rule.kt, this entity includes columns fortype,filed,check,value,sender_id,sender_list(JSON array),sender_logic,sms_template,regex_replace,sim_slot,status,time, andtitle. It maintains a foreign key relationship toSender.idwith cascade delete and update. -
Sender– Defines notification endpoints inapp/src/main/kotlin/cn/ppps/forwarder/database/entity/Sender.kt. Columns includetype,name,json_setting(configuration payload),status, andtime. -
Msg– Stores incoming message content inapp/src/main/kotlin/cn/ppps/forwarder/database/entity/Msg.kt. Trackstype,from,content,sim_slot,sim_info,sub_id,time, andcall_type. -
Logs– Records forwarding attempts inapp/src/main/kotlin/cn/ppps/forwarder/database/entity/Logs.kt. Containstype,msg_id,rule_id,sender_id,forward_status,forward_response, andtime. Foreign keys referenceMsg.id,Rule.id, andSender.idwith cascade deletion. -
Task– Manages automated tasks inapp/src/main/kotlin/cn/ppps/forwarder/database/entity/Task.kt. Storestype,name,description,conditions,actions,last_exec_time,next_exec_time, andstatus. -
Frpc– Configures frp client settings inapp/src/main/kotlin/cn/ppps/forwarder/database/entity/Frpc.kt. Usesuid(String) as primary key alongsidename,config,autorun, andtime.
Database View
LogsDetail – A read-only view defined in app/src/main/kotlin/cn/ppps/forwarder/database/entity/LogsDetail.kt that joins Logs, Rule, and Sender tables. The UI layer queries this view to display enriched forwarding history without manual joins.
AppDatabase Configuration
The central database class AppDatabase (located at app/src/main/kotlin/cn/ppps/forwarder/database/AppDatabase.kt) configures the Room database holder with the following characteristics:
- Annotation:
@Database(entities = [Rule::class, Sender::class, Msg::class, Logs::class, Task::class, Frpc::class], views = [LogsDetail::class], version = 21, exportSchema = false) - Singleton Pattern: Implements
getInstance(context)usingRoom.databaseBuilder()to ensure single database access across the application lifecycle. - DAO Exposure: Declares abstract methods for each DAO (e.g.,
abstract fun ruleDao(): RuleDao).
Data Access Objects (DAO) Layer
Each entity has a corresponding DAO interface in the app/src/main/kotlin/cn/ppps/forwarder/database/dao/ package. These interfaces define standard CRUD operations using Room annotations:
@Insertfor creation operations@Updatefor modification operations@Deletefor removal operations@Querywith SQLite statements for custom reads
The DAOs contain no business logic; they serve as pure abstraction layers over SQL statements.
Repository Pattern Implementation
Repository classes in app/src/main/kotlin/cn/ppps/forwarder/database/repository/ provide high-level suspend functions that wrap DAO calls. This pattern decouples the ViewModel layer from Room-specific implementation details.
Repository Structure
Each repository follows this consistent pattern:
class RuleRepository(context: Context) {
private val dao = AppDatabase.getInstance(context).ruleDao()
suspend fun insert(rule: Rule) = dao.insert(rule)
suspend fun update(rule: Rule) = dao.update(rule)
suspend fun delete(rule: Rule) = dao.delete(rule)
fun getAll(): LiveData<List<Rule>> = dao.getAll()
fun findById(id: Long): LiveData<Rule?> = dao.findById(id)
}
Key benefits of this architecture:
- Testability: ViewModels depend on repository interfaces rather than Room directly
- Consistency: All data access flows through typed suspend functions
- Lifecycle awareness: Repositories expose
LiveDatastreams for automatic UI updates
Database Migration Strategy
The migration chain spans versions 1 through 21, handling schema evolution without data loss. Critical migration steps include:
- Version 2 → 3: Adds
sim_slotcolumn to theRuletable to support dual-SIM filtering. - Version 5 → 6: Introduces
typecolumn to bothRuleand legacyLogtables for categorization. - Version 11 → 12: Adds
sender_list(JSON-encoded) and removes singlesender_idfromRule, enabling multi-sender dispatch. - Version 13 → 14: Splits monolithic
Logtable into separateMsgandLogstables with foreign key relationships. - Version 14 → 15: Creates the
LogsDetailview for optimized UI queries. - Version 20 → 21: Adds user-editable
titlecolumn toRulefor custom labeling.
These migrations execute automatically when AppDatabase initializes with a higher version number than the existing database file.
Practical Code Examples
Inserting a New Rule
val rule = Rule(
id = 0,
type = "sms",
filed = FILED_MSG_CONTENT,
check = CHECK_CONTAIN,
value = "验证码",
senderId = 0,
smsTemplate = "",
regexReplace = "",
simSlot = SIM_SLOT_ALL,
status = 1,
time = Date(),
senderList = listOf(),
senderLogic = "ALL",
silentPeriodStart = 0,
silentPeriodEnd = 0,
silentDayOfWeek = "",
title = "验证码转发"
)
val repo = RuleRepository(context)
lifecycleScope.launch {
repo.insert(rule)
}
Querying Enabled Senders
val senderRepo = SenderRepository(context)
senderRepo.getAllEnabled().observe(this) { senders ->
// Update UI with active sender configurations
}
Accessing LogsDetail View
val logsDao = AppDatabase.getInstance(context).logsDao()
val logsDetail = MutableLiveData<List<LogsDetail>>()
viewModelScope.launch {
logsDetail.value = logsDao.getLogsDetail()
}
Summary
- SmsForwarder implements a Room database at version 21 with six entities (
Rule,Sender,Msg,Logs,Task,Frpc) and one view (LogsDetail). - The
AppDatabasesingleton manages schema definitions and exposes DAOs through abstract methods. - Repository classes wrap DAOs to provide suspend functions and
LiveDatastreams, isolating Room implementation from ViewModels. - Migration scripts handle schema evolution from version 1 to 21, including table splits, JSON column additions, and foreign key relationships.
- Source files are organized under
app/src/main/kotlin/cn/ppps/forwarder/database/with clear separation betweenentity,dao, andrepositorypackages.
Frequently Asked Questions
What database does SmsForwarder use?
SmsForwarder uses Android Room, an abstraction layer over SQLite provided by the Android Jetpack libraries. The database file is created automatically by Room.databaseBuilder() in the AppDatabase.getInstance() method, with the current schema version set to 21.
How does SmsForwarder handle database schema updates?
The application implements incremental migrations defined as static Migration objects in AppDatabase.kt. When the app updates and detects a higher version number, Room executes the migration chain (e.g., MIGRATION_20_21 adds the title column to Rule) while preserving existing user data.
Where is the repository pattern implemented in SmsForwarder?
Repository classes reside in app/src/main/kotlin/cn/ppps/forwarder/database/repository/. Each repository (such as RuleRepository or SenderRepository) obtains the AppDatabase singleton and exposes high-level functions like insert(), update(), and getAll(), hiding the underlying DAO implementation from ViewModels.
What is the LogsDetail view used for?
LogsDetail is a Room database view created in version 14→15 that joins Logs, Rule, and Sender tables. The UI layer queries this view through LogsDao.getLogsDetail() to display forwarding history with enriched sender and rule metadata without executing manual SQL joins in application code.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →