更改 T 的属性时如何使 LiveData<MutableList<T>> 更新?

Posted

技术标签:

【中文标题】更改 T 的属性时如何使 LiveData<MutableList<T>> 更新?【英文标题】:How to make LiveData<MutableList<T>> update when I change a property of T? 【发布时间】:2022-01-02 09:56:30 【问题描述】:

我正在制作一个应用程序,它通过向某些 url 发出请求并记录需要多长时间来获取(伪)延迟值。

首先,我使用改造从 Web 服务器获取 JSON 响应。此响应包含:主机名称(例如 Ebay UK)、主机 url(例如 www.ebay.co.uk)和图像 url。我将此响应映射到我的数据类,如下所示:

data class(
    val name: String,
    var url: String,
    val icon: String,
    var averagePing: Long = -1
)

url 是一个 var 属性,因为在进行调用以获取延迟值之前,我需要添加 https:// 才能发出请求。

我正在这样做:

fun getHostsLiveData() 
    viewModelScope.launch(Dispatchers.IO) 
        val hostList = repo.getHosts()
        for (host in hostList) 
            host.url = "https://" + host.url
            host.averagePing = -1
        
        hostListLiveData.postValue(hostList)//updated the recyclerview with initial values
        //with default (-1) value of averagePing

        for (host in hostList) 
            async  pingHostAndUpdate(host.url, hostList) 
        
    

第一个 for 循环准备我的数据。 for 循环之后的行将数据提交给回收器适配器,以便立即显示主机名、url 和图标(这一切都有效,即我有一个 LiveData 的工作观察者),而我正在等待延迟价值观。

第二个 for 循环调用该函数来计算每个主机的延迟值,并且 updateHostList() 函数更新 LiveData。

这是函数的外观:

suspend fun pingHostAndUpdate(url: String, hostList: MutableList<Host>) 
    try 
        val before = Calendar.getInstance().timeInMillis
        val connection = URL(url).openConnection() as HttpURLConnection //Need error handling
        connection.connectTimeout = 5*1000
        connection.connect()
        val after = Calendar.getInstance().timeInMillis
        connection.disconnect()
        val diff = after - before
        updateHostList(url, diff, hostList)
     catch (e: MalformedURLException) 
        Log.e("MalformedURLExceptionTAG", "MalformedURLException")
     catch (e: IOException) 
        Log.e("IOExceptionTAG", "IOException")
    


fun updateHostList(url: String, pingResult: Long, hostList: MutableList<Host>) 
    //All this on mainThread
    var foundHost: Host? = null
    var index = 0
    for (host in hostListLiveData.value!!)  
        if (host.url == url) 
            foundHost = host
            break
        
        index++
     
    if (foundHost != null) 
        viewModelScope.launch(Dispatchers.Main) 
            val host =  Host(foundHost.name, foundHost.url, foundHost.icon, pingResult)
            Log.d("TAAAG", "$host") 
            hostList[index] = host
            hostListLiveData.value = hostList
        
    

所有这些都发生在 viewModel 中。目前,当我更改列表的一个元素的一个属性时,我正在通过再次提交整个列表来更新我的列表,这对我来说似乎很可怕。

我的问题是:如何只更新主机的一个属性并让它自动刷新 UI?

提前致谢

编辑:我的观察者看起来像这样:

viewModel.hostListLiveData.observe(this, Observer  adapter.updateData(it) )

updateData() 看起来像这样:

fun updateData(freshHostList: List<Host>) 
    hostList.clear()
    hostList.addAll(freshHostList)
    notifyDataSetChanged()

@ArpitShukla,你建议我有 2 个更新功能吗?一个用于显示初始列表,另一个用于更新列表项?还是我将 notifyDataSetChanged() 和 notifyItemChanged() 都放在 updateData() 中?

Edit2:更改了我的函数调用以使其异步。

【问题讨论】:

您最关心的是哪一部分?多次将新列表传递给回收站视图可能会影响性能? (顺便说一句,您的 pingHost2 函数没有并行运行。您使用一台主机调用它,然后立即等待响应)。 @ArpitShukla 传递一个新列表会导致回收器视图重新下载图像,这看起来有问题。我知道 ping 功能不好,我是 Kotlin 的新手,我现在的首要任务是了解 LiveData。 【参考方案1】:

您可以考虑在adapter 中使用notifyItemChanged(position) 而不是notifyDataSetChanged() 更新从hostListLiveData 观察到的项目。

notifyItemChanged(position)是一个item change事件,它只更新item的内容。

编辑: 您正在使用notifyDataSetChanged() 更新导致重新布局和重新绑定您不期望的RecyclerView 的数据内容。因此,您应该使用notifyItemChanged(position) 更新数据内容。

我认为您可以创建一个新功能来更新适配器中的RecyclerView,例如

fun updateHostAndPing(updatedHost: Host, position: Int) 
    hostList[position].apply 
        url = updatedHost.url
        averagePing = updatedHost.averagePing
    
    notifyItemChanged(position)

在您的观察者中,您可能需要检查它是新列表还是更新列表

viewModel.hostListLiveData.observe(this, Observer  
    if (adapter.itemCount == ZERO) 
        adapter.updateData(it) 
     else 
        it.forEachIndexed  index, host ->
            adapter.updateHostAndPing(host, index) 
        
    
)

【讨论】:

这可行,但我认为这与 LiveData 无关。我制作了一个简单的回收器视图,其中显示了一个列表来测试它,我只是调用了 adapter.notifyItemChanged(position)。这确实有效,但我看不出它与 LiveData 有什么关系。请你澄清一下好吗? P.S.:我将更新显示我的观察者如何工作的问题,我认为这将为您提供更多背景 是的,这与 LiveData 无关,这是因为更新 RecyclerView 的方式。您正在使用notifyDataSetChanged() 更新数据内容(例如更新hostping)。 notifyDataSetChanged() 将完全重新绑定并重新布局所有可见数据。 我也尝试过使用ListAdapter 而不是RecyclerView.Adapter,它也实现了我想要的功能。您知道使用notifyDataSetChanged()ListAdapter 哪个更好吗?据我了解notifyDataSetChanged(),它会更新您告诉它更新的视图(RecyclerView 中的行)。ListAdapter 检查新列表和旧列表中的差异,然后更新更改的字段(即TextView) 到新值(虽然我不确定它是更新唯一的TextView 还是整行,在这种情况下没有区别?)。 ListAdapter 在后台使用AsyncListDiffer 来帮助计算存储数据和提供数据之间的差异,以及它如何比较数据是基于DiffUtil.ItemCallback 中定义的条件。 AFAIK,ListAdapter 不会重新布局您的RecyclerView,而只会更新修改后的数据。好吧,无论如何,ListAdapter 也是一个可行的解决方案

以上是关于更改 T 的属性时如何使 LiveData<MutableList<T>> 更新?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Room 和 LiveData 保持 RecyclerView 顺序的更改?

在 Android 视图模型中的内部网络更改回调时 LiveData 未触发 - Kotlin

DataBinding 和 LiveData :两种实现(Kotlin 和 Java),不能使 Java impl 工作

如何使下拉箭头(插入符号图标引导程序)在单击时更改?

使用 LiveData 时检查 RoomDatabase 是不是为空

对象字段更改的 LiveData 更新