Search code examples
androidandroid-recyclerviewandroid-databindingandroid-jetpack

Data binding with RecyclerView not rendering correclty


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:

enter image description here

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
    }
}

Solution

  • 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