Search code examples
androidkotlinandroid-recyclerviewandroid-binder

Use of binding breaks RecyclerView item layout


I am building an Android app with Kotlin and decided to replace the calls to findViewById and use binding. It all works fine but specifically, when I change an Adapter for a RecyclerView it breaks the item layout.

Original code with findViewById:

class WeightListAdapter(val weights: List<WeightWithPictures>, val onWeightItemClickListener: OnWeightItemClickListener) : RecyclerView.Adapter<WeightListAdapter.WeightHolder>()  {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WeightListAdapter.WeightHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.list_item_weight, parent, false)
        return WeightHolder(view)
    }

    override fun onBindViewHolder(holder: WeightListAdapter.WeightHolder, position: Int) {
        val weightWithPictures = weights[position]
        holder.bind(weightWithPictures)
    }

    override fun getItemCount() = weights.size

    inner class WeightHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
        private lateinit var weight: Weight
        private val weightValueView: TextView = this.itemView.findViewById(R.id.weightValue)
        private val weightDateView: TextView = this.itemView.findViewById(R.id.weightDate)
        private val weightImageView: ImageView = this.itemView.findViewById(R.id.weightImage) as ImageView

And this is the layout:

enter image description here

But then whenever I use binding:

class WeightListAdapter(val weights: List<WeightWithPictures>, val onWeightItemClickListener: OnWeightItemClickListener) : RecyclerView.Adapter<WeightListAdapter.WeightHolder>()  {

    private var _binding: ListItemWeightBinding? = null
    private val binding get() = _binding!!

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WeightListAdapter.WeightHolder {
        _binding = ListItemWeightBinding.inflate(LayoutInflater.from(parent.context))
        val view = binding.root
        return WeightHolder(view)
    }

    override fun onBindViewHolder(holder: WeightListAdapter.WeightHolder, position: Int) {
        val weightWithPictures = weights[position]
        holder.bind(weightWithPictures)
    }

    override fun getItemCount() = weights.size

    inner class WeightHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
        private lateinit var weight: Weight
        private val weightValueView: TextView = binding.weightValue
        private val weightDateView: TextView = binding.weightDate
        private val weightImageView: ImageView = binding.weightImage

The layout breaks:

enter image description here

Any ideas about what am I doing wrong here? Is it a bug?

P.S - For now, I am just adding the annotation to ignore bindings as documented here for the item view but I would really like to understand what's wrong.


Solution

  • Your binding needs to be inflated in the context of its parent so its root view's layout params will take effect:

    binding = ListItemWeightBinding.inflate(LayoutInflater.from(parent.context), parent, false)
    

    I think it will also give you problems that you are creating a binding property for the Adapter if you try to use it long term. Each ViewHolder holds a distinct view with a distinct binding instance. It's working now because you use it only for the ViewHolder instantiation immediately after setting each instance. But if that's all your intent is, you should just pass the binding to the constructor of your ViewHolder and omit the adapter's property.

    By the way, this is the sort of pattern I use for ViewHolders. Less code. Note, it doesn't have to be an inner class.

    class WeightHolder(binding: ListItemWeightBinding) : RecyclerView.ViewHolder(binding.root), View.OnClickListener {
    
        fun bind(item: WeightWithPictures) {
            with (binding) {
                // set data for views here
            }
        }
    }