here is the situation:
I have a RecyclerView
in a Fragment
using DataBinding
. Its adapter is a ListAdapter
.
class MyFragment : Fragment() {
override fun onCreateView(...) {
val binding = // inflate layout with DataBindingUtil
val myListAdapter = MyListAdapter()
binding.recyclerView.adapter = myListAdapter
val myViewModel: MyViewModel by activityViewModels()
myViewModel.myLiveData.observe(viewLifecycleOwner, Observer {
println("before submit: ${myListAdapter.currentList}")
myListAdapter.submitList(it)
println("after submit: ${myListAdapter.currentList}")
})
return binding.root
}
}
Data to display is represented by a List<Double>
. Once set, the list is used to initialize a MutableLiveData
as follows:
class MyViewModel : ViewModel() {
val data = listOf<Double>(/* init with some values */)
val myLiveData = MutableLiveData(data)
fun onButtonClicked() {
update()
println(myLiveData.value) // LiveData is correctly updated according to data
}
}
Note: the
update()
method changes the list's values like:data[0] = someValue
. It does not useclear()
noraddAll()
to update it.
- In addition, I override
ListAdapter.onCurrentListChanged()
only to put aprintln()
inside.
At this point, nothing peculiar. When the fragment is created, the RecyclerView
shows the initial list. In Logcat:
I/System.out: before submit: []
I/System.out: onCurrentListChanged: [] -> [4.07, 6.29, 13.85, 17.92, 21.42, 23.47, 1.85]
I/System.out: after submit: [4.07, 6.29, 13.85, 17.92, 21.42, 23.47, 1.85]
For now, when I click on the button, the UI does not refresh because I did not assign a new list but only update the current one. Besides, if I navigate to another fragment then come back, the RecyclerView
is displaying the updated values.
So I add the following at the end of onButtonClicked()
:
myLiveData.value = myLiveData.value
But this does not work, after clicking on the button the UI is still not updating automatically. Moreover, the Logcat prints only the 'before submit' and 'after submit', with the same ListAdapter.currentList
value:
I/System.out: before submit: [4.88, 6.77, 13.85, 17.76, 20.93, 22.69, 1.85]
I/System.out: after submit: [4.88, 6.77, 13.85, 17.76, 20.93, 22.69, 1.85]
To me, prints should rather look like this:
I/System.out: before submit: [4.07, 6.29, 13.85, 17.92, 21.42, 23.47, 1.85]
I/System.out: onCurrentListChanged: [4.07, 6.29, 13.85, 17.92, 21.42, 23.47, 1.85] -> [4.88, 6.77, 13.85, 17.76, 20.93, 22.69, 1.85]
I/System.out: after submit: [4.88, 6.77, 13.85, 17.76, 20.93, 22.69, 1.85]
It appears that ListAdapter.currentList
has already the updated value even before calling submitList()
. I guess this is why the RecyclerView
is not refreshed since the submitted list is the same as the current one.
I also tried the following at the end of onButtonClicked()
with no success:
//myLiveData.value = myLiveData.value
myLiveData.value = emptyList()
myLiveData.value = data
And this...
//myLiveData.value = myLiveData.value
myLiveData.value = emptyList()
//myLiveData.value = data
... gives me that:
I/System.out: before submit: [4.88, 6.77, 13.85, 17.76, 20.93, 22.69, 1.85]
I/System.out: after submit: [4.88, 6.77, 13.85, 17.76, 20.93, 22.69, 1.85]
I/System.out: onCurrentListChanged: [4.88, 6.77, 13.85, 17.76, 20.93, 22.69, 1.85] -> []
Now onCurrentListChanged()
is called but at the end of the 2 prints instead of in the middle.
Also, ListAdapter.currentList
seems to contain values in the 'after submit' prints but is actually empty in the onCurrentListChanged()
print. Confusing...
The only way I found to make the RecyclerView
refresh is to call notifyDataSetChanged()
. But then, there is no benefit to use ListAdapter
and its submitList()
method.
As a newbie, I may have done something not correctly but I do not know what.
The problem is only about the UI not refreshing. I can see in Locgcat that data is properly updating.
Thanks in advance
EDIT
class MyListAdapter() : ListAdapter<Double, MyViewHolder>(MyDiffCallBack()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
MyViewHolder.from(parent, itemCount)
override fun onBindViewHolder(holder: MyViewHolder, position: Int) =
holder.bind(getItem(position))
override fun onCurrentListChanged(previousList: MutableList<Double>, currentList: MutableList<Double>) {
super.onCurrentListChanged(previousList, currentList)
println("onCurrentListChanged: $previousList -> $currentList")
}
}
class MyViewHolder private constructor(private val binding: MyItemViewBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(data: Double) {
binding.dataTxt.text = data.toString()
}
companion object {
fun from(parent: ViewGroup, itemCount: Int): MyViewHolder {
val binding = // inflate layout
binding.root.layoutParams.height = parent.height / itemCount // set the height proportionally
return MyViewHolder(binding)
}
}
}
class MyDiffCallBack : DiffUtil.ItemCallback<Double>() {
override fun areItemsTheSame(oldItem: Double, newItem: Double): Boolean = oldItem == newItem
override fun areContentsTheSame(oldItem: Double, newItem: Double): Boolean = oldItem == newItem
}