I am trying to use data binding with RecyclerView. I set up a simple application that shows the numbers 1 to 50, one on each row, in a RecyclerView. And I want to use data binding to each row.
Here is the code of my adapter (Note that I called dataBinding.executePendingBindings()
):
class SimpleAdapter
: ListAdapter<Int, SimpleAdapter.SimpleAdapterViewHolder>(SimpleAdapterDiffUtil()) {
inner class SimpleAdapterViewHolder(
private val dataBinding: SimpleAdapterViewHolderBinding
) : RecyclerView.ViewHolder(dataBinding.root) {
fun bind(str: String) {
Log.e("MYTAG", "setting TextView: $str")
dataBinding.txtView.text = str
dataBinding.executePendingBindings()
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SimpleAdapterViewHolder {
val binding = SimpleAdapterViewHolderBinding.inflate(
LayoutInflater.from(parent.context), parent, false)
return SimpleAdapterViewHolder(binding)
}
override fun onBindViewHolder(holder: SimpleAdapterViewHolder, position: Int) {
val stringValue = getItem(position)
holder.bind(stringValue.toString())
}
}
private class SimpleAdapterDiffUtil: DiffUtil.ItemCallback<Int>() {
override fun areItemsTheSame(oldItem: Int, newItem: Int): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: Int, newItem: Int): Boolean {
return oldItem == newItem
}
}
And this is the view XML
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="text"
type="String" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/txt_view"
android:text="@{text}"
android:layout_width="match_parent"
android:layout_height="36dp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/black"
app:layout_constraintTop_toBottomOf="@id/txt_view"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
And here is what it does:
As you can see, in the initial screen, none of the rows were not properly rendered. When I scrolled down, I found that it started rendering from row 23. Then if I scrolled back up, I can finally see the prior rows being filled.
If I change the code to use plain old views, rather than data bindings, it works as expected. Code here:
// This works fine!
class SimpleAdapter
: ListAdapter<Int, SimpleAdapter.SimpleAdapterViewHolder>(SimpleAdapterDiffUtil()) {
inner class SimpleAdapterViewHolder(
private val rootView: View
) : RecyclerView.ViewHolder(rootView) {
fun bind(str: String) {
Log.e("MYTAG", "setting TextView: $str")
rootView.findViewById<TextView>(R.id.txt_view).text = str
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SimpleAdapterViewHolder {
val rootView = LayoutInflater.from(parent.context)
.inflate(R.layout.simple_adapter_view_holder, parent, false)
return SimpleAdapterViewHolder(rootView)
}
override fun onBindViewHolder(holder: SimpleAdapterViewHolder, position: Int) {
val stringValue = getItem(position)
holder.bind(stringValue.toString())
}
}
private class SimpleAdapterDiffUtil: DiffUtil.ItemCallback<Int>() {
override fun areItemsTheSame(oldItem: Int, newItem: Int): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: Int, newItem: Int): Boolean {
return oldItem == newItem
}
}
As I see in your xml, you're setting android: text="@{text}"
In other words, textView value is being set from binding.text
You set dataBinding.txtView.text = str
and it will update the text. After that using executePendingDataBinding
it's value will be reset to that dataBinding.text
(aka android: text="@{text}"
) which is null or empty. So you shoud set
dataBinding.text = str
Instead of
dataBinding.txtView.text = str