首页 > 解决方案 > 房间不使用 @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)

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课堂有关,这里是:

@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我的数据库中的条目没有得到更新。实际上,我想用上面提供的代码更新的唯一字段是isAdded,但在我按下btnAdd列表项后它保持不变。我使用 Android Studio 的 Database Inspector 来验证这一点。

我尝试@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工作?

标签: androidandroid-roomandroid-mvvm

解决方案


您可能已经理解,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) })

推荐阅读