Reputation: 332
I'm making an app which gets the (pseudo) latency values by making a request to some urls and recording how long that will take.
First, I use retrofit to get a JSON response from a web server. This response contains: the name of the host (e.g. Ebay UK), the url of the host (e.g. www.ebay.co.uk), and an image url. I map this response onto my data class which looks like the following:
data class(
val name: String,
var url: String,
val icon: String,
var averagePing: Long = -1
)
url is a var property as before making the calls to get the latency values, I need to add https:// in order to make the request.
I'm doing all of this like so:
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) }
}
}
}
The first for loop prepares my data. The line after the for loop submits the data to the recycler adapter, in order to show the host name, url and icon straight away (this all works i.e. I have a working observer for the LiveData), while I'm waiting for the latency values.
The second for loop calls the function to calculate the latency values for each host and the updateHostList() function updates the LiveData.
This is how the functions look:
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
}
}
}
All of this happens in the viewModel. Currently I'm updating my list by submitting the entire list again when I change one property of one element of the list, which seems horrible to me.
My question is: How can I update only one property of host and have it refresh the UI automatically?
Thanks in advance
Edit: My observer looks like this:
viewModel.hostListLiveData.observe(this, Observer { adapter.updateData(it) })
And updateData() looks like this:
fun updateData(freshHostList: List<Host>) {
hostList.clear()
hostList.addAll(freshHostList)
notifyDataSetChanged()
}
@ArpitShukla, do you suggest I would have 2 update functions? one for showing the initial list and another to update on item of the list? Or would I just put both notifyDataSetChanged() and notifyItemChanged() in updateData()?
Edit2: changed my function call to make it async.
Upvotes: 0
Views: 695
Reputation: 614
You can consider to update items observed from hostListLiveData
using notifyItemChanged(position)
instead notifyDataSetChanged()
in your adapter
.
notifyItemChanged(position)
is an item change event, which update only the content of the item.
EDIT:
You're using notifyDataSetChanged()
on updating the content of data which causing to relayout and rebind the RecyclerView
which you're not expecting. Therefore you should update the content of your data using notifyItemChanged(position)
.
I think you may create a new function for updating your RecyclerView
in the adapter e.g.
fun updateHostAndPing(updatedHost: Host, position: Int) {
hostList[position].apply {
url = updatedHost.url
averagePing = updatedHost.averagePing
}
notifyItemChanged(position)
}
and in your observer, you may need to check whether it is fresh list or and updated list
viewModel.hostListLiveData.observe(this, Observer {
if (adapter.itemCount == ZERO) {
adapter.updateData(it)
} else {
it.forEachIndexed { index, host ->
adapter.updateHostAndPing(host, index)
}
}
})
Upvotes: 1