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

Remove item using LiveData and ViewModel causes re-emitting

我有一个显示项目列表的片段,从视图模型中观察(来自 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) 
}

ViewModel

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 一起再次发出,因此片段尝试再次从适配器中删除该项目,但它崩溃了。

我该如何解决这个问题?

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

现在可以正常使用了。如果有人认为这是一个糟糕的解决方案,请告诉我。

与其从适配器中删除单个项目,还不如更新 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 时,这是一个常见问题。解释了几种解决方案 here and here。它们要么包装发出的数据,要么包装观察者。在这个包装器中,他们存储了一个标志,用于跟踪事件是否已经 handled/emitted。