房间不使用 @Update(onConflict = OnConflictStrategy.REPLACE) 更新实体

Room not updating entities with @Update(onConflict = OnConflictStrategy.REPLACE)

我的应用程序使用 Google Places API 稍后我使用这些数据从 openweather 获取天气。 我有一个 SearchFragmentRecyclerView 发生这种情况的地方。

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)

我的 repositoryupdate() 方法如下所示:

    suspend fun update(place: PlacesPrediction, added: Boolean) =
        database.dao().update(place.copy(isAdded = added))

最后,我的 daoupdate:

@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

}

所以,我的问题是我数据库中的 PlacesPredictions 条目没有得到更新。实际上,我想用上面提供的代码更新的唯一字段是 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) })