【问题标题】:Remove item using LiveData and ViewModel causes re-emitting使用 LiveData 和 ViewModel 删除项目会导致重新发射
【发布时间】: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 尝试再次从适配器中删除该项目,它崩溃了。

我该如何解决这个问题?

【问题讨论】:

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

标签: android android-fragments android-lifecycle android-livedata


【解决方案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 用于应该只发生一次的事件时,这是一个常见问题。 herehere 解释了几种解决方案。它们要么包装发出的数据,要么包装观察者。在这个包装器中,它们存储了一个标志,用于跟踪事件是否已被处理/发出。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-10-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-02-13
      • 1970-01-01
      • 2019-11-15
      相关资源
      最近更新 更多