Как обновить LiveData<MutableList<T><T>> при изменении свойства T?

0

Вопрос

Я создаю приложение, которое получает значения (псевдо) задержки, отправляя запрос на некоторые URL-адреса и записывая, сколько времени это займет.

Во-первых, я использую retrofit для получения ответа 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 отправляет данные адаптеру recycler, чтобы сразу отобразить имя хоста, 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.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: изменил вызов моей функции, чтобы сделать его асинхронным.

android-livedata kotlin
2021-11-23 22:53:04
1

Лучший ответ

1

Вы можете рассмотреть возможность обновления элементов, наблюдаемых с hostListLiveData с помощью notifyItemChanged(position) вместо notifyDataSetChanged() В вашем adapter.

notifyItemChanged(position) это событие изменения элемента, которое обновляет только содержимое элемента.

ИЗМЕНИТЬ:
Вы используете 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) 
        }
    }
})
2021-11-24 23:12:08

Это работает, но я не думаю, что это связано с LiveData. Я сделал простое представление переработчика, которое показывает список для проверки этого, и я только что вызвал adapter.notifyItemChanged(позиция). Это сработало, но я не вижу, как это связано с LiveData. Не могли бы вы уточнить, пожалуйста? P.S.: Я обновлю вопрос, показывающий, как работает мой наблюдатель, я думаю, что это даст вам больше контекста
SpawnTheTronix

Да, дело не в живых данных, а в том, как обновляется RecyclerView. Вы используете notifyDataSetChanged() об обновлении содержимого ваших данных (например, обновление host и ping). В notifyDataSetChanged() будет полностью выполнена повторная привязка и ретрансляция всех видимых данных.
Putra Nugraha

Я также пробовал использовать ListAdapter вместо RecyclerView.Adapter, он также достиг желаемой функциональности. Знаете ли вы, что лучше, используя notifyDataSetChanged() или в ListAdapter? Насколько я понимаю notifyDataSetChanged(), он обновляет представление (строка в RecyclerView) который вы велите ему обновить.ListAdapter проверяет наличие различий в новом списке и старом списке, а затем обновляет измененное поле (т. е. TextView) к новому значению (хотя я не уверен, что оно обновляет только TextView или всю строку, и в этом случае не было бы никакой разницы?).
SpawnTheTronix

ListAdapter под капотом находится использование AsyncListDiffer чтобы помочь рассчитать различия между сохраненными данными и предоставленными данными, а также способ их сравнения основан на определенном условии в DiffUtil.ItemCallback. АФАИК, ListAdapter не будет передавать ваши RecyclerView, но обновляйте только измененные данные. Что ж, ListAdapter в любом случае это также жизнеспособное решение для вашего случая
Putra Nugraha

На других языках

Эта страница на других языках

Italiano
..................................................................................................................
Polski
..................................................................................................................
Română
..................................................................................................................
한국어
..................................................................................................................
हिन्दी
..................................................................................................................
Français
..................................................................................................................
Türk
..................................................................................................................
Česk
..................................................................................................................
Português
..................................................................................................................
ไทย
..................................................................................................................
中文
..................................................................................................................
Español
..................................................................................................................
Slovenský
..................................................................................................................