# 02 — Interactors Each feature under `dev.achmad.ledgerr.domain..interactor` owns a set of interactor classes. Interactors are use-case classes that can hold logic and depend on whatever they need (DAOs, Android context, etc.). The UI layer depends only on domain — never on data directly. Naming convention for methods: - `await(...)` — suspend, returns a single value - `awaitAll(...)` — suspend, returns a list - `subscribeOne(...)` — returns `Flow` - `subscribeAll(...)` — returns `Flow>` - Filters are parameters, not separate functions --- ## Feature: expense ### `GetExpenses` ```kotlin class GetExpenses(dao: ExpenseDao, categoryDao: CategoryDao) { fun subscribeAll(): Flow> fun subscribeByDateRange(range: DateRange): Flow> suspend fun awaitOne(id: Long): ExpenseWithCategory? suspend fun awaitAll(query: String = "", range: DateRange? = null): List } ``` ### `UpsertExpense` ```kotlin class UpsertExpense(dao: ExpenseDao) { suspend fun await(expense: Expense): Long // insert if id=0, update otherwise } ``` ### `InsertExpenses` ```kotlin class InsertExpenses(dao: ExpenseDao) { suspend fun awaitAll(expenses: List) // bulk insert, single transaction } ``` ### `DeleteExpense` ```kotlin class DeleteExpense(dao: ExpenseDao) { suspend fun await(id: Long) } ``` ### `ReassignExpenseCategory` ```kotlin class ReassignExpenseCategory(dao: ExpenseDao) { suspend fun await(fromCategoryId: Long, toCategoryId: Long) } ``` ### `GetExpenseSummary` ```kotlin class GetExpenseSummary(dao: ExpenseDao, categoryDao: CategoryDao) { suspend fun await(range: DateRange): ExpenseSummary } ``` --- ## Feature: category ### `GetCategories` ```kotlin class GetCategories(dao: CategoryDao) { fun subscribeAll(): Flow> suspend fun awaitOne(id: Long): Category? suspend fun awaitAll(): List suspend fun awaitDefault(): Category // returns the isDefault=true "Uncategorized" category } ``` ### `UpsertCategory` ```kotlin class UpsertCategory(dao: CategoryDao) { suspend fun await(category: Category): Long } ``` ### `DeleteCategory` ```kotlin class DeleteCategory( dao: CategoryDao, reassignExpenseCategory: ReassignExpenseCategory, getCategories: GetCategories, ) { suspend fun await(id: Long) // internally: reassign orphaned expenses to "Uncategorized", then delete } ``` ### `SeedDefaultCategories` ```kotlin class SeedDefaultCategories(dao: CategoryDao) { suspend fun await() // no-op if categories already exist } ``` --- ## Feature: recurring ### `GetRecurringExpenses` ```kotlin class GetRecurringExpenses(dao: RecurringExpenseDao, categoryDao: CategoryDao) { fun subscribeAll(): Flow> suspend fun awaitOne(id: Long): RecurringExpense? } ``` ### `UpsertRecurringExpense` ```kotlin class UpsertRecurringExpense(dao: RecurringExpenseDao) { suspend fun await(recurring: RecurringExpense): Long } ``` ### `DeleteRecurringExpense` ```kotlin class DeleteRecurringExpense(dao: RecurringExpenseDao) { suspend fun await(id: Long) // does NOT delete expense instances already created from this template } ``` ### `ProcessDueRecurringExpenses` ```kotlin class ProcessDueRecurringExpenses( recurringDao: RecurringExpenseDao, expenseDao: ExpenseDao, ) { suspend fun await(today: LocalDate = LocalDate.now()): List } ``` --- ## Feature: bankstatement ### `BankStatementImporter` (interface) ```kotlin interface BankStatementImporter { val bankName: String suspend fun await(pdfUri: Uri): Result> } ``` ### `ImportBRIBankStatement : BankStatementImporter` ```kotlin class ImportBRIBankStatement(context: Context) : BankStatementImporter { override val bankName = "BRI" override suspend fun await(pdfUri: Uri): Result> // stub } ``` ### `ImportJagoBankStatement : BankStatementImporter` ```kotlin class ImportJagoBankStatement(context: Context) : BankStatementImporter { override val bankName = "Jago" override suspend fun await(pdfUri: Uri): Result> // stub } ``` ### `ImportBNIBankStatement : BankStatementImporter` ```kotlin class ImportBNIBankStatement(context: Context) : BankStatementImporter { override val bankName = "BNI" override suspend fun await(pdfUri: Uri): Result> // stub } ``` `DomainModule` binds all three as `List` so the UI can show a bank picker. --- ## Feature: export ### `ExportExpensesToCsv` ```kotlin class ExportExpensesToCsv(expenseDao: ExpenseDao, categoryDao: CategoryDao, context: Context) { suspend fun await(range: DateRange, outputUri: Uri): Result } ``` --- ## Feature: data Wipe-all-data administrative action. Triggered from `SettingsScreen` "Clear all data" `AlertDialogPreference`. ### `ClearAllData` ```kotlin class ClearAllData(database: AppDatabase) { suspend fun await() // deletes all rows from every table } ```