I am trying to update values in a room database when a button is clicked in the UI. I am creating an inventory app that allows the user to increase the number of item pieces by one each time a button is clicked. The new number of pieces should be displayed in the UI and the database should be updated.
I have created a method to update the database in the Dao. I have confirmed that the id/primaryKey of the target row and the model are similar. I have also tried inserting (with onConflictStategy.REPLACE) instead of updating. The program works fine and there are no compile-time errors however clicking the button does not update the database. Help me figure out what I am missing. Here is the code.
Entity
/**
* Entity data class represents a single row in the database.
*/
@Entity(tableName = "invent")
data class Item (
@PrimaryKey(autoGenerate = true)
val id: Int,
val name: String,
val category: String,
val unit: String,
val pieces: Int,
@ColumnInfo(name = "piecesBought", defaultValue = "0")
val piecesBought: Int,
val price: Int
)
Dao
@Dao
interface ItemDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(item: Item)
@Update(onConflict = OnConflictStrategy.REPLACE)
suspend fun update(item: Item)
@Delete
suspend fun delete(item: Item)
@Query("SELECT * from invent ORDER BY name ASC")
fun getAllItems(): Flow<List<Item>>
@Query("SELECT * from invent WHERE id = :id")
fun getItem(id: Int): Flow<Item?>
@Query("SELECT * from invent WHERE name like '%'||:name||'%'")
fun getItemByName(name: String): Flow<List<Item?>>
}
Repository
class OfflineItemRepository(private val itemDao: ItemDao): ItemRepository {
override fun getAllItemsStream(): Flow<List<Item>> = itemDao.getAllItems()
override fun getItemByNameStream(name: String): Flow<List<Item?>> = itemDao.getItemByName(name)
override fun getItemStream(id: Int): Flow<Item?> = itemDao.getItem(id)
override suspend fun insertItem(item: Item) = itemDao.insert(item)
override suspend fun deleteItem(item: Item) = itemDao.delete(item)
override suspend fun updateItem(item: Item) = itemDao.update(item)
}
viewModel
The function increasePiecesBoughtByOne
is called when the button in the UI is clicked.
class ShoppingViewModel (itemRepository: ItemRepository): ViewModel() {
private val myRepo = itemRepository
private var _piecesBought = MutableStateFlow("")
val piecesBought = _piecesBought.asStateFlow()
/**
* function to handle adding items to basket on + button click
* enables the Added to basket button
*/
fun increasePiecesBoughtByOne(item: Item) {
// run database operations inside a coroutine.
viewModelScope.launch {
withContext(Dispatchers.IO) {
when {
item.piecesBought < item.pieces -> {
myRepo.updateItem(item.copy(piecesBought = (item.piecesBought + 1)))
_piecesBought.value = item.piecesBought.toString()
}
}
}
}
}
}
UI
The value of TextField is given by the piecesBought
variable from the viewModel as shown below.
I want the piecesBought value to be increased by one each time onAddItem(item)
is called when the button is clicked but it is not working.
The function increasePiecesBoughtByOne
is called when the button in the TextField is clicked.
I have confirmed that the function is called when the button is clicked by checking Logcat. However piecesBought
does not change. Why isn't it changing?
val piecesBought by viewModel.piecesBought.collectAsStateWithLifecycle()
TextField(
value = piecesBought,
onValueChange = {
onPiecesBoughtChange(it, item)
},
singleLine = true,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
modifier = modifier.weight(1f, fill = true)
)
Spacer(modifier.size(ButtonDefaults.IconSpacing))
Icon(imageVector = Icons.Default.AddCircle,
contentDescription = "Add item to cart",
modifier = modifier.clickable {
onAddItem(item)
}
)
Database
Database migration from version 1 to version 2 involved creating a new column piecesBought
@Database(
entities = [Item::class],
version = 2,
exportSchema = true,
autoMigrations = [AutoMigration(from = 1, to = 2)]
)
abstract class DukaDatabase: RoomDatabase() {
abstract fun itemDao(): ItemDao
companion object {
@Volatile
private var Instance: DukaDatabase? = null
fun getDatabase(context: Context): DukaDatabase {
// if the Instance is not null, return it, otherwise create a new database instance.
return Instance ?: synchronized(this) {
Room.databaseBuilder(context, DukaDatabase::class.java, "duka_database")
.createFromAsset("database/inventory_db.db")
//.fallbackToDestructiveMigration()
.build().also { Instance = it }
}
}
}
}
The UI was not updating because I was not using flow to update it as pointed out by Arek KubiĆski in the comments. I resolved this by combining 2 flows which would cause a UI update if any of their values changed. The 2 flows are
newPieces
and_searchText
in the code below. I will focus on only one of the flows which is triggered when the button is clicked (newPieces
). The other flow (_searchText
) is tied to a TextField and is updated when the text in the TextField changes. It operates like a searchfield and helps filtersearchList
which is displayed in the UI.Combined flow
Below is the combined flow which updates
searchList
which is the list displayed by the UI. If the_searchText
variable or_newPieces
variable changed, it would trigger a call to updatesearchList
.Function to increase count on button click
This is the function that is called when the button is clicked. As part of this function body I included
_newPieces.emit(item.piecesBought)
that emits a value each time the function is called. This results in a change of thenewPieces
sharedFlow value. This is what causes an update ofsearchList
and a change in the UI.MutableSharedFlow
newPieces.collect{}
collects the new value emitted by_newPieces.emit(item.piecesBought)
and triggers an update of the UI through a change insearchList