Move DB I/O to Dispatchers.IO in AddEdit Expense/Recurring screen models #21

Closed
opened 2026-06-28 13:20:04 +00:00 by admin · 0 comments
Owner

Overview

AddEditExpenseScreenModel and AddEditRecurringScreenModel both perform DB I/O inside screenModelScope.launch { ... } without switching off the main dispatcher:

  • AddEditExpenseScreenModel.kt:54-55getExpenses.awaitOne(id) on edit-mode init
  • AddEditExpenseScreenModel.kt:99-102upsertExpense.await(expense) in save()
  • AddEditRecurringScreenModel.kt:63-64getRecurringExpenses.awaitOne(id) on edit-mode init
  • AddEditRecurringScreenModel.kt:123-126upsertRecurringExpense.await(recurring) in save()

screenModelScope runs on Dispatchers.Main by default, so Room calls execute on the main thread. These need to run on Dispatchers.IO.

What to do

Wrap the interactor await* calls in withContext(Dispatchers.IO) { ... } in both screen models, so the suspending DB work happens off the main thread while the state-flow updates still run on the main dispatcher.

For each of the four call sites above:

screenModelScope.launch {
    val result = withContext(Dispatchers.IO) {
        upsertExpense.await(expense)
    }
    // ...state updates on Main
}

Imports to add to both files:

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

Architectural note (optional follow-up)

A more durable fix is to push the dispatcher switch into the interactor implementations (e.g. a base class or each await* method), so every caller is safe by construction. Out of scope for this issue unless the user asks — keep this PR focused on the two screen models.

Acceptance

  • AddEditExpenseScreenModel wraps getExpenses.awaitOne and upsertExpense.await in withContext(Dispatchers.IO)
  • AddEditRecurringScreenModel wraps getRecurringExpenses.awaitOne and upsertRecurringExpense.await in withContext(Dispatchers.IO)
  • State-flow updates still happen on the main dispatcher (inside launch { ... } after the withContext block)
  • ./gradlew assembleDebug succeeds

Implementation rule

Per AGENTS.md — do not start implementation without explicit user sign-off on this issue. When working, check for related issues in the remote repo first.

## Overview `AddEditExpenseScreenModel` and `AddEditRecurringScreenModel` both perform DB I/O inside `screenModelScope.launch { ... }` without switching off the main dispatcher: - `AddEditExpenseScreenModel.kt:54-55` — `getExpenses.awaitOne(id)` on edit-mode init - `AddEditExpenseScreenModel.kt:99-102` — `upsertExpense.await(expense)` in `save()` - `AddEditRecurringScreenModel.kt:63-64` — `getRecurringExpenses.awaitOne(id)` on edit-mode init - `AddEditRecurringScreenModel.kt:123-126` — `upsertRecurringExpense.await(recurring)` in `save()` `screenModelScope` runs on `Dispatchers.Main` by default, so Room calls execute on the main thread. These need to run on `Dispatchers.IO`. ## What to do Wrap the interactor `await*` calls in `withContext(Dispatchers.IO) { ... }` in both screen models, so the suspending DB work happens off the main thread while the state-flow updates still run on the main dispatcher. For each of the four call sites above: ```kotlin screenModelScope.launch { val result = withContext(Dispatchers.IO) { upsertExpense.await(expense) } // ...state updates on Main } ``` Imports to add to both files: ```kotlin import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext ``` ## Architectural note (optional follow-up) A more durable fix is to push the dispatcher switch into the interactor implementations (e.g. a base class or each `await*` method), so every caller is safe by construction. Out of scope for this issue unless the user asks — keep this PR focused on the two screen models. ## Acceptance - [ ] `AddEditExpenseScreenModel` wraps `getExpenses.awaitOne` and `upsertExpense.await` in `withContext(Dispatchers.IO)` - [ ] `AddEditRecurringScreenModel` wraps `getRecurringExpenses.awaitOne` and `upsertRecurringExpense.await` in `withContext(Dispatchers.IO)` - [ ] State-flow updates still happen on the main dispatcher (inside `launch { ... }` *after* the `withContext` block) - [ ] `./gradlew assembleDebug` succeeds ## Implementation rule Per `AGENTS.md` — do not start implementation without explicit user sign-off on this issue. When working, check for related issues in the remote repo first.
admin closed this issue 2026-06-28 13:30:20 +00:00
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: admin/ledgerr#21