# Database Schema and Repository Pattern in SmsForwarder: Room Architecture Explained

> Explore the SmsForwarder database schema and repository pattern. Learn how Room architecture, six entities, and DAO abstraction empower your ViewModel layer.

- Repository: [pppscn/SmsForwarder](https://github.com/pppscn/SmsForwarder)
- Tags: architecture
- Published: 2026-06-22

---

**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

1. **`Rule`** – Stores forwarding rules with filtering logic. Located in [`app/src/main/kotlin/cn/ppps/forwarder/database/entity/Rule.kt`](https://github.com/pppscn/SmsForwarder/blob/main/app/src/main/kotlin/cn/ppps/forwarder/database/entity/Rule.kt), this entity includes columns for `type`, `filed`, `check`, `value`, `sender_id`, `sender_list` (JSON array), `sender_logic`, `sms_template`, `regex_replace`, `sim_slot`, `status`, `time`, and `title`. It maintains a foreign key relationship to `Sender.id` with cascade delete and update.

2. **`Sender`** – Defines notification endpoints in [`app/src/main/kotlin/cn/ppps/forwarder/database/entity/Sender.kt`](https://github.com/pppscn/SmsForwarder/blob/main/app/src/main/kotlin/cn/ppps/forwarder/database/entity/Sender.kt). Columns include `type`, `name`, `json_setting` (configuration payload), `status`, and `time`.

3. **`Msg`** – Stores incoming message content in [`app/src/main/kotlin/cn/ppps/forwarder/database/entity/Msg.kt`](https://github.com/pppscn/SmsForwarder/blob/main/app/src/main/kotlin/cn/ppps/forwarder/database/entity/Msg.kt). Tracks `type`, `from`, `content`, `sim_slot`, `sim_info`, `sub_id`, `time`, and `call_type`.

4. **`Logs`** – Records forwarding attempts in [`app/src/main/kotlin/cn/ppps/forwarder/database/entity/Logs.kt`](https://github.com/pppscn/SmsForwarder/blob/main/app/src/main/kotlin/cn/ppps/forwarder/database/entity/Logs.kt). Contains `type`, `msg_id`, `rule_id`, `sender_id`, `forward_status`, `forward_response`, and `time`. Foreign keys reference `Msg.id`, `Rule.id`, and `Sender.id` with cascade deletion.

5. **`Task`** – Manages automated tasks in [`app/src/main/kotlin/cn/ppps/forwarder/database/entity/Task.kt`](https://github.com/pppscn/SmsForwarder/blob/main/app/src/main/kotlin/cn/ppps/forwarder/database/entity/Task.kt). Stores `type`, `name`, `description`, `conditions`, `actions`, `last_exec_time`, `next_exec_time`, and `status`.

6. **`Frpc`** – Configures frp client settings in [`app/src/main/kotlin/cn/ppps/forwarder/database/entity/Frpc.kt`](https://github.com/pppscn/SmsForwarder/blob/main/app/src/main/kotlin/cn/ppps/forwarder/database/entity/Frpc.kt). Uses `uid` (String) as primary key alongside `name`, `config`, `autorun`, and `time`.

### Database View

**`LogsDetail`** – A read-only view defined in [`app/src/main/kotlin/cn/ppps/forwarder/database/entity/LogsDetail.kt`](https://github.com/pppscn/SmsForwarder/blob/main/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`](https://github.com/pppscn/SmsForwarder/blob/main/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)` using `Room.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:

- `@Insert` for creation operations
- `@Update` for modification operations  
- `@Delete` for removal operations
- `@Query` with 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:

```kotlin
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 `LiveData` streams 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_slot` column to the `Rule` table to support dual-SIM filtering.
- **Version 5 → 6**: Introduces `type` column to both `Rule` and legacy `Log` tables for categorization.
- **Version 11 → 12**: Adds `sender_list` (JSON-encoded) and removes single `sender_id` from `Rule`, enabling multi-sender dispatch.
- **Version 13 → 14**: Splits monolithic `Log` table into separate `Msg` and `Logs` tables with foreign key relationships.
- **Version 14 → 15**: Creates the `LogsDetail` view for optimized UI queries.
- **Version 20 → 21**: Adds user-editable `title` column to `Rule` for 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

```kotlin
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

```kotlin
val senderRepo = SenderRepository(context)
senderRepo.getAllEnabled().observe(this) { senders ->
    // Update UI with active sender configurations
}

```

### Accessing LogsDetail View

```kotlin
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 **`AppDatabase`** singleton manages schema definitions and exposes DAOs through abstract methods.
- **Repository classes** wrap DAOs to provide suspend functions and `LiveData` streams, 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 between `entity`, `dao`, and `repository` packages.

## 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`](https://github.com/pppscn/SmsForwarder/blob/main/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.