房间不使用 @Update(onConflict = OnConflictStrategy.REPLACE) 更新实体
Room not updating entities with @Update(onConflict = OnConflictStrategy.REPLACE)
我的应用程序使用 Google Places API 稍后我使用这些数据从 openweather 获取天气。
我有一个 SearchFragment
和 RecyclerView
发生这种情况的地方。
在 SearchFragment
中,我观察了我得到的列表:
viewModel.predictions.observe(viewLifecycleOwner) {
citiesAdapter.submitList(it)
}
<...>
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_fragment_weather, menu)
<...>
searchView.onQueryTextChanged {
viewModel.searchQuery.value = it
}
}
我的viewModel
:
class SearchViewModel @Inject constructor(
private val repository: AutocompleteRepository,
private val weatherRepository: WeatherRepository
) : ViewModel() {
fun provideClient(client: PlacesClient) {
repository.provideClient(client)
}
val searchQuery = MutableStateFlow("")
private val autocompleteFlow = searchQuery.flatMapLatest {
repository.getPredictions(it)
}
val predictions = autocompleteFlow.asLiveData()
fun onAddPlace(place: PlacesPrediction, added: Boolean) {
viewModelScope.launch {
repository.update(place, added)
if (added) weatherRepository.addWeather(place)
else weatherRepository.delete(place)
}
}
fun onDestroy() = viewModelScope.launch {repository.clearDb()}
}
里面 adapter
我这样绑定我的物品:
inner class CityViewHolder(private val binding: ItemCityToAddBinding) : RecyclerView.ViewHolder(binding.root) {
init {
binding.apply {
btnAdd.setOnClickListener {
val position = adapterPosition
if (position != RecyclerView.NO_POSITION) {
val place = getItem(position)
btnAdd.animate().rotation(if (place.isAdded) 45f else 0f).start()
println("Current item state (isAdded): ${place.isAdded}")
listener.onAddClick(place, !place.isAdded)
}
}
}
}
fun bind(prediction : PlacesPrediction) {
binding.apply {
val cityName = prediction.fullText.split(", ")[0]
locationName.text = cityName
fullName.text = prediction.fullText
btnAdd.animate().rotation(if (prediction.isAdded) 45f else 0f).start()
}
}
}
其中 listener
作为参数从我的片段传递到我的适配器:
override fun onAddClick(place: PlacesPrediction, isAdded: Boolean) {
viewModel.onAddPlace(place, isAdded)
println("Parameter passed to onClick: $isAdded, placeId = ${place.placeId}")
}
<...>
val citiesAdapter = CitiesAdapter(this)
我的 repository
的 update()
方法如下所示:
suspend fun update(place: PlacesPrediction, added: Boolean) =
database.dao().update(place.copy(isAdded = added))
最后,我的 dao
的 update
:
@Update(onConflict = OnConflictStrategy.REPLACE)
suspend fun update(prediction: PlacesPrediction)
这一切都与 PlacesPrediction
class 有关,这里是:
@Entity(tableName = "autocomplete_table")
data class PlacesPrediction(
val fullText: String,
val latitude: Double,
val longitude: Double,
val placeId: String,
val isAdded: Boolean = false
) {
@PrimaryKey(autoGenerate = true) var id: Int = 0
}
所以,我的问题是我数据库中的 PlacesPrediction
s 条目没有得到更新。实际上,我想用上面提供的代码更新的唯一字段是 isAdded
,但在我按列表项的 btnAdd
后它保持不变。我使用 Android Studio 的数据库检查器来验证这一点。
我尝试使用 @Insert
来代替:
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(prediction: PlacesPrediction)
suspend fun update(place: PlacesPrediction, added: Boolean) =
database.dao().insert(place.copy(isAdded = added))
但奇怪的是它只插入了place
的副本,我点击的原始项目保持不变。
解决方法
只有通过破解才能获得所需的行为:
@Entity(tableName = "autocomplete_table")
data class PlacesPrediction(
val fullText: String,
val latitude: Double,
val longitude: Double,
val placeId: String,
var isAdded: Boolean = false,
@PrimaryKey(autoGenerate = true) var id: Int = 0
)
suspend fun update(place: PlacesPrediction, added: Boolean) =
database.dao().insert(place.copy(isAdded = added, id = place.id))
而且我一点也不喜欢这个方案。所以我的问题是:如何让 @Update
工作?
正如您可能已经理解的那样,数据 类 的生成 copy
方法忽略了在构造函数外声明的所有成员。因此 place.copy(isAdded = added)
将生成所有构造函数参数的副本,但将 id 保留为默认值 0,这意味着应该插入一个新元素,而不是更新现有元素。
这是我个人的看法:
将 id 作为构造函数参数是最优雅的解决方案,因为更新将开箱即用。
但是,如果您非常不喜欢它,也许扩展功能可能会对您有所帮助:
inline fun PlacesPrediction.preserveId(copyBuilder: PlacesPrediction.() -> PlacesPrediction): PlacesPrediction{
val copy = copyBuilder(this)
copy.id = this.id
return copy
}
//usage
suspend fun update(place: PlacesPrediction, added: Boolean) =
database.dao().insert(place.preserveId { copy(isAdded = added) })
我的应用程序使用 Google Places API 稍后我使用这些数据从 openweather 获取天气。
我有一个 SearchFragment
和 RecyclerView
发生这种情况的地方。
在 SearchFragment
中,我观察了我得到的列表:
viewModel.predictions.observe(viewLifecycleOwner) {
citiesAdapter.submitList(it)
}
<...>
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_fragment_weather, menu)
<...>
searchView.onQueryTextChanged {
viewModel.searchQuery.value = it
}
}
我的viewModel
:
class SearchViewModel @Inject constructor(
private val repository: AutocompleteRepository,
private val weatherRepository: WeatherRepository
) : ViewModel() {
fun provideClient(client: PlacesClient) {
repository.provideClient(client)
}
val searchQuery = MutableStateFlow("")
private val autocompleteFlow = searchQuery.flatMapLatest {
repository.getPredictions(it)
}
val predictions = autocompleteFlow.asLiveData()
fun onAddPlace(place: PlacesPrediction, added: Boolean) {
viewModelScope.launch {
repository.update(place, added)
if (added) weatherRepository.addWeather(place)
else weatherRepository.delete(place)
}
}
fun onDestroy() = viewModelScope.launch {repository.clearDb()}
}
里面 adapter
我这样绑定我的物品:
inner class CityViewHolder(private val binding: ItemCityToAddBinding) : RecyclerView.ViewHolder(binding.root) {
init {
binding.apply {
btnAdd.setOnClickListener {
val position = adapterPosition
if (position != RecyclerView.NO_POSITION) {
val place = getItem(position)
btnAdd.animate().rotation(if (place.isAdded) 45f else 0f).start()
println("Current item state (isAdded): ${place.isAdded}")
listener.onAddClick(place, !place.isAdded)
}
}
}
}
fun bind(prediction : PlacesPrediction) {
binding.apply {
val cityName = prediction.fullText.split(", ")[0]
locationName.text = cityName
fullName.text = prediction.fullText
btnAdd.animate().rotation(if (prediction.isAdded) 45f else 0f).start()
}
}
}
其中 listener
作为参数从我的片段传递到我的适配器:
override fun onAddClick(place: PlacesPrediction, isAdded: Boolean) {
viewModel.onAddPlace(place, isAdded)
println("Parameter passed to onClick: $isAdded, placeId = ${place.placeId}")
}
<...>
val citiesAdapter = CitiesAdapter(this)
我的 repository
的 update()
方法如下所示:
suspend fun update(place: PlacesPrediction, added: Boolean) =
database.dao().update(place.copy(isAdded = added))
最后,我的 dao
的 update
:
@Update(onConflict = OnConflictStrategy.REPLACE)
suspend fun update(prediction: PlacesPrediction)
这一切都与 PlacesPrediction
class 有关,这里是:
@Entity(tableName = "autocomplete_table")
data class PlacesPrediction(
val fullText: String,
val latitude: Double,
val longitude: Double,
val placeId: String,
val isAdded: Boolean = false
) {
@PrimaryKey(autoGenerate = true) var id: Int = 0
}
所以,我的问题是我数据库中的 PlacesPrediction
s 条目没有得到更新。实际上,我想用上面提供的代码更新的唯一字段是 isAdded
,但在我按列表项的 btnAdd
后它保持不变。我使用 Android Studio 的数据库检查器来验证这一点。
我尝试使用 @Insert
来代替:
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(prediction: PlacesPrediction)
suspend fun update(place: PlacesPrediction, added: Boolean) =
database.dao().insert(place.copy(isAdded = added))
但奇怪的是它只插入了place
的副本,我点击的原始项目保持不变。
解决方法
只有通过破解才能获得所需的行为:
@Entity(tableName = "autocomplete_table")
data class PlacesPrediction(
val fullText: String,
val latitude: Double,
val longitude: Double,
val placeId: String,
var isAdded: Boolean = false,
@PrimaryKey(autoGenerate = true) var id: Int = 0
)
suspend fun update(place: PlacesPrediction, added: Boolean) =
database.dao().insert(place.copy(isAdded = added, id = place.id))
而且我一点也不喜欢这个方案。所以我的问题是:如何让 @Update
工作?
正如您可能已经理解的那样,数据 类 的生成 copy
方法忽略了在构造函数外声明的所有成员。因此 place.copy(isAdded = added)
将生成所有构造函数参数的副本,但将 id 保留为默认值 0,这意味着应该插入一个新元素,而不是更新现有元素。
这是我个人的看法:
将 id 作为构造函数参数是最优雅的解决方案,因为更新将开箱即用。
但是,如果您非常不喜欢它,也许扩展功能可能会对您有所帮助:
inline fun PlacesPrediction.preserveId(copyBuilder: PlacesPrediction.() -> PlacesPrediction): PlacesPrediction{
val copy = copyBuilder(this)
copy.id = this.id
return copy
}
//usage
suspend fun update(place: PlacesPrediction, added: Boolean) =
database.dao().insert(place.preserveId { copy(isAdded = added) })