【问题标题】:Room not updating entities with @Update(onConflict = OnConflictStrategy.REPLACE)房间不使用 @Update(onConflict = OnConflictStrategy.REPLACE) 更新实体
【发布时间】:2021-03-02 18:01:47
【问题描述】:

我的应用程序使用 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 类有关,这里是:

@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 的 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 工作?

【问题讨论】:

    标签: android android-room android-mvvm


    【解决方案1】:

    你可能已经理解了,数据类生成的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) })
    

    【讨论】:

    • 因此,基本上使用 REPLACE 冲突策略,insertupdate 查询的行为是相同的,前提是您也复制了条目的 ID。好的,谢谢指出!至于我对这种方法的反感,没什么特别的,我只是认为这是一种不好的做法,因为忘记将 id 复制到某处并稍后处理错误是微不足道的。有什么办法可以更新它吗?覆盖copy() 是一种选择吗?无论如何,谢谢
    • “忘记在某处复制 id”是什么意思?将实体映射到另一个对象或使用数据类的copy 方法时?
    • 使用copy 方法时。想象一个用例,其中多个viewModels 使用相同的dao 来执行数据库操作。假设您忘记将实体的id 放入您在viewModels 中使用的一些copy 方法中。
    • 如果 id 是构造函数的一部分,copy 也会自动复制 id,除非您明确覆盖它
    猜你喜欢
    • 2019-10-07
    • 1970-01-01
    • 1970-01-01
    • 2019-11-16
    • 2019-01-17
    • 1970-01-01
    • 1970-01-01
    • 2022-11-09
    • 1970-01-01
    相关资源
    最近更新 更多