使用 LiveData 和 ViewModel 删除项目会导致重新发射

Posted

技术标签:

【中文标题】使用 LiveData 和 ViewModel 删除项目会导致重新发射【英文标题】:Remove item using LiveData and ViewModel causes re-emitting 【发布时间】:2021-01-21 08:32:47 【问题描述】:

我有一个显示项目列表的片段,从视图模型观察(从 http 服务,它们没有保存在数据库中)。现在,我需要删除其中一项。我有一个删除结果实时数据,因此视图可以观察项目何时被删除。

片段

fun onViewCreated(view: View, savedInstanceState: Bundle?) 
    //...
    viewModel.deleteItemLiveData.observe(viewLifecycleOwner) 
        when (it.status) 
            Result.Status.ERROR -> showDeletingError()
            Result.Status.SUCCESS -> 
                itemsAdapter.remove(it.value)
                commentsAdapter.notifyItemRemoved(it.value)
            
        
    



fun deleteItem(itemId: String, itemIndex: Int) = lifecycleScope.launch 
    viewModel.deleteItem(itemId, itemIndex) 

视图模型

val deleteItemLiveData = MutableLiveData<Result<Int>>()

suspend fun deleteItem(itemId: String, itemIndex: Int) = withContext(Dispatchers.IO) 
    val result = service.deleteItem(itemId)
    withContext(Dispatchers.Main) 
        if (result.success) 
            deleteItemLiveData.value = Result.success(itemIndex)
         else 
            deleteItemLiveData.value = Result.error()
        
    

它工作正常,但是当我导航到另一个片段并再次返回时出现问题。 deleteItemLIveData 与最后一个 Result 再次发出,因此 Fragment 尝试再次从适配器中删除该项目,它崩溃了。

我该如何解决这个问题?

【问题讨论】:

为什么片段在加载时会检索到陈旧的项目列表?为什么它没有得到更新的列表? 我能做到。但在那种情况下,我会遇到同样的问题。如何查看删除结果,例如显示错误消息而不在每次恢复时重新显示? 这是一个架构问题,您正在尝试从适配器中删除已删除的项目,而不是收听对删除做出反应的更新的项目列表。 我理解你。但想象一下,我侦听更新的列表,而不是从适配器中删除该项目。在那种情况下,我坚持,如何查看观察删除结果,以防万一失败,在屏幕上显示错误? 我添加了一个答案,解释了我将如何处理这个问题。关于错误,不应将其与存储库中的数据相结合,在删除时向用户显示就足够了。 【参考方案1】:

与其从适配器中删除单个项目,不如更新 LiveData 的原始源,因为视图会观察该列表。

项目存储库应处理删除,从 LiveData 中删除该项目,然后将更新传播到视图,然后传播到适配器。

回购可能看起来像这样......

fun deleteItem(item: Item): Result 
    val updated = items.value
    updated.remove(item)
    items.postValue(updated)
    . . .
    // propagate result of success/failure back to the view


fun observeItems() = items

在您的片段中,您将从单个 LiveData 源获得即时更新

fun onViewCreated(view: View, savedInstanceState: Bundle?) 
    viewModel.observeItems().observe(viewLifecycleOwner) 
             itemsAdapter.update(it) //use DiffUtil to update list or notifyDataSetChanged
        
    

显示错误应该是上下文相关的、祝酒词或一些视觉通知。

更新: 删除中的句柄错误可能看起来像这样,我想不到......

suspend fun deleteItem(itemId: String, itemIndex: Int): Result = withContext(Dispatchers.IO) 
    val result = service.deleteItem(itemId)
    withContext(Dispatchers.Main) 
        if (result.success) 
            // push updated list to items
            val updated = items.value
            updated.remove(item)
            items.postValue(updated)
            Result.Success()
         else 
            Result.error()
        
    

【讨论】:

删除时出现错误,视图如何通知? 我已经用作为概念返回的通用结果更新了我的答案,您可以将其映射到您的答案并从视图中观察操作,或者拥有您的视图观察到的 LiveData 流,由你决定。但我强烈主张对项目列表使用单一的观察者。单一的事实来源。【参考方案2】:

我找到了解决方案。我更改了我的代码,因此片段从onCreate 方法而不是onViewCreated 观察。我也换了主人。而不是viewLifecycleOwner 现在是this。这样,在片段恢复时不会重新发出值,而是在创建或专门调用 viewModel.deleteItem 时重新发出值。

它现在工作正常。如果有人认为这是一个不好的解决方案,请告诉我。

【讨论】:

【参考方案3】:

当您将LiveData 用于应该只发生一次的事件时,这是一个常见问题。 here 和 here 解释了几种解决方案。它们要么包装发出的数据,要么包装观察者。在这个包装器中,它们存储了一个标志,用于跟踪事件是否已被处理/发出。

【讨论】:

以上是关于使用 LiveData 和 ViewModel 删除项目会导致重新发射的主要内容,如果未能解决你的问题,请参考以下文章

JetpackLiveData 架构组件 ( LiveData 简介 | LiveData 使用方法 | ViewModel + LiveData 示例 )

使用 ViewModel 和 LiveData 多次改造执行 API

MVVM 架构,ViewModel和LiveData

MVVM 架构,ViewModel和LiveData

Android Jetpack 学习之旅--> ViewModel & LiveData 的使用

MVVM 架构,ViewModel和LiveData