Feature: Deleting a Word
Relevant source files
The following files were used as context for generating this wiki page:- app/src/main/java/com/vocabri/ui/screens/dictionarydetails/viewmodel/DictionaryDetailsViewModel.kt
- domain/src/main/java/com/vocabri/domain/usecase/word/DeleteWordUseCase.kt
- data/src/main/java/com/vocabri/data/datasource/word/WordLocalDataSourceImpl.kt
- app/src/test/java/com/vocabri/ui/dictionarydetails/WordItemTest.kt
- app/src/main/java/com/vocabri/ui/screens/dictionarydetails/viewmodel/DictionaryDetailsEvent.kt
- data/src/test/java/com/vocabri/data/WordRepositoryImplTest.kt
- domain/src/test/java/com/vocabri/domain/DeleteWordUseCaseTest.kt
- app/src/main/java/com/vocabri/di/AppModule.kt
The "Deleting a Word" feature provides users with the ability to remove words from their personal dictionary. The primary user interaction is a swipe-to-delete gesture on a word within the dictionary details list. This action initiates a top-down data flow through the application's layered architecture, from the UI layer to the data layer, ensuring the word is removed from the local database.
The implementation follows a clean architecture pattern, separating UI, business logic, and data persistence concerns. This ensures the feature is maintainable, testable, and robust. The process is designed to be responsive, with the UI updating optimistically before the background deletion operation completes.
Architecture Overview
The deletion process involves components from the app, domain, and data modules. The flow is unidirectional, initiated by a user gesture and propagated down through ViewModels, Use Cases, Repositories, and finally to the Data Source that interacts with the database.
This diagram illustrates the dependency flow between the key components involved in the deletion feature.
Detailed Flow
The deletion of a word is a multi-step process that begins with a user action and ends with a database transaction.
1. User Interaction (UI Layer)
The user initiates the deletion by swiping a WordListItem composable from end to start (left swipe in LTR layouts) within the DictionaryDetailsScreen. This gesture is tested to ensure it triggers the correct event.
Sources: app/src/test/java/com/vocabri/ui/dictionarydetails/WordItemTest.kt:78-88
The WordListItem then sends a DictionaryDetailsEvent.DeleteWordClicked event, containing the ID of the word to be deleted, to the DictionaryDetailsViewModel.
// From app/src/test/java/com/vocabri/ui/dictionarydetails/WordItemTest.kt
composeRule.onNodeWithText("lernen")
.performTouchInput { swipeLeft() }
composeRule.waitForIdle()
assertEquals(
listOf(DictionaryDetailsEvent.DeleteWordClicked(word.id)),
events,
)
Sources: app/src/test/java/com/vocabri/ui/dictionarydetails/WordItemTest.kt:82-88
2. Event Handling (ViewModel)
The DictionaryDetailsViewModel receives the event and orchestrates the deletion process.
- The
handleEventfunction pattern-matches the incoming event. - Upon receiving
DeleteWordClicked, it calls the privatehandleDeleteWordfunction. handleDeleteWordperforms an optimistic UI update. It immediately filters the deleted word out of the current state's word list. This provides instant visual feedback to the user.- It then calls
deleteWordInternallyto perform the actual deletion in the background.
Sources: app/src/main/java/com/vocabri/ui/screens/dictionarydetails/viewmodel/DictionaryDetailsViewModel.kt:71-74, app/src/main/java/com/vocabri/ui/screens/dictionarydetails/viewmodel/DictionaryDetailsViewModel.kt:144-150
// From app/src/main/java/com/vocabri/ui/screens/dictionarydetails/viewmodel/DictionaryDetailsViewModel.kt
private fun handleDeleteWord(wordId: String) {
val currentState = _state.value
if (currentState is DictionaryDetailsState.WordsLoaded) {
val newList = currentState.words.filterNot { it.id == wordId }
_state.update { currentState.copy(words = newList) }
}
deleteWordInternally(wordId)
}
3. Business Logic (Domain Layer)
The deleteWordInternally function in the ViewModel launches a coroutine to execute the DeleteWordUseCase. This use case encapsulates the single business rule of deleting a word.
The DeleteWordUseCase has one responsibility: to call the deleteWordById method on the WordRepository interface. This abstraction decouples the ViewModel from the data layer's implementation details.
// From domain/src/main/java/com/vocabri/domain/usecase/word/DeleteWordUseCase.kt
class DeleteWordUseCase(private val wordRepository: WordRepository) {
// ...
suspend fun execute(id: String) {
log.i { "Executing DeleteWordUseCase for word ID: $id" }
wordRepository.deleteWordById(id)
log.i { "Word with ID $id successfully deleted" }
}
}
Sources: domain/src/main/java/com/vocabri/domain/usecase/word/DeleteWordUseCase.kt:25-36
4. Data Persistence (Data Layer)
The WordRepositoryImpl (the implementation of the WordRepository interface) delegates the call to the appropriate data source. In this case, it calls deleteWord on both the localDataSource and remoteDataSource.
Sources: data/src/test/java/com/vocabri/data/WordRepositoryImplTest.kt:115-125
The WordLocalDataSourceImpl contains the final step: interacting with the SQLDelight database. It calls the generated deleteWordById query function. The implementation notes that ON DELETE CASCADE is used in the database schema, which automatically handles the removal of related records (like translations and examples) when a word is deleted.
// From data/src/main/java/com/vocabri/data/datasource/word/WordLocalDataSourceImpl.kt
override suspend fun deleteWord(wordId: String) {
log.i { "Deleting word by ID = $wordId" }
database.wordQueries.deleteWordById(wordId)
log.i { "Word with ID = $wordId deleted successfully" }
}
Sources: data/src/main/java/com/vocabri/data/datasource/word/WordLocalDataSourceImpl.kt:282-287
Sequence Diagram
This diagram shows the sequence of interactions across the different architectural layers when a user deletes a word.
Sources: app/src/test/java/com/vocabri/ui/dictionarydetails/WordItemTest.kt:82-88, app/src/main/java/com/vocabri/ui/screens/dictionarydetails/viewmodel/DictionaryDetailsViewModel.kt:153-167, domain/src/main/java/com/vocabri/domain/usecase/word/DeleteWordUseCase.kt:33, data/src/main/java/com/vocabri/data/datasource/word/WordLocalDataSourceImpl.kt:284
Component Summary
The table below summarizes the key classes involved in the word deletion feature.
| Component | Layer | Module | Description |
|---|---|---|---|
WordListItem | UI | app | A Jetpack Compose UI component that displays a word and handles the swipe-to-delete gesture. |
DictionaryDetailsViewModel | ViewModel | app | Manages the state for the details screen, handles the DeleteWordClicked event, and calls the use case. |
DeleteWordUseCase | Domain | domain | Encapsulates the business logic for deleting a word, delegating the operation to the WordRepository. |
WordRepository | Domain | domain | An interface defining the contract for word data operations, including deletion. |
WordRepositoryImpl | Data | data | The implementation of WordRepository that coordinates between local and remote data sources. |
WordLocalDataSourceImpl | Data | data | Interacts directly with the local SQLDelight database to execute the deleteWordById query. |
Sources: app/src/test/java/com/vocabri/ui/dictionarydetails/WordItemTest.kt, app/src/main/java/com/vocabri/ui/screens/dictionarydetails/viewmodel/DictionaryDetailsViewModel.kt, domain/src/main/java/com/vocabri/domain/usecase/word/DeleteWordUseCase.kt, data/src/test/java/com/vocabri/data/WordRepositoryImplTest.kt, data/src/main/java/com/vocabri/data/datasource/word/WordLocalDataSourceImpl.kt
Conclusion
The "Deleting a Word" feature is a well-architected piece of functionality within the Vocabri application. It demonstrates a clear separation of concerns, an offline-first approach, and a responsive user experience through optimistic UI updates. The use of a dedicated DeleteWordUseCase and repository pattern makes the logic easy to test and maintain, as confirmed by unit tests at the domain and data layers.
Sources: domain/src/test/java/com/vocabri/domain/DeleteWordUseCaseTest.kt, data/src/test/java/com/vocabri/data/WordRepositoryImplTest.kt