36

How to do latest jetpack "View binding" in adapter, for automatically binding the views. I am not using the findVeiwById or Butterknife or Kotlin synthetic??? I have used the new view binding and is is working fine for Activity and for Fragment. I am able to see the ActivityHomeBinding and FragmentHomeBinding files after enabling the viewBinding in build.gradle file. Also, I am seeing the class ItemListBinding for the item xml used i.e. item_list.xml. But how to use this file in the adapter of the recyclerview

viewBinding {
        enabled = true
}

Home Activity file

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityHomeBinding.inflate(layoutInflater)
        setContentView(binding.root)
}

Fragment

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        binding = FragmentHomeBinding.inflate(inflater, container, false)
        val view = binding.root
        return view
}

Base Adapter: Want to use view binding here. I am able to see ItemListBinding, but not getting how to use it.

class BaseAdapter @Inject constructor(
    private val context: Context,
    private val picasso: Picasso
) :
    RecyclerView.Adapter<BaseAdapter.ViewHolder>() {

    private var data = ArrayList<Data>()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        return ViewHolder(
            LayoutInflater.from(context).inflate(R.layout.item_list, parent, false)
        )
    }

    override fun getItemCount() = data.size

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        with(holder) {
              // TODO
        }
    }

    class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {

        @BindView(R.id.tvMovieName)
        lateinit var nameTV: TextView

        @BindView(R.id.imageView)
        lateinit var bannerImage: ImageView

        init {
            ButterKnife.bind(this@ViewHolder, view)
        }
    }

    fun setData(serverData: ArrayList<Data>) {
        data = serverData
        notifyDataSetChanged()
    }
}
DevAnuragGarg
  • 1,592
  • 2
  • 14
  • 18
  • 1
    Hi @DevAnuragGarg it been long hope you doing well, I have created a blog post covering view binding in-depth with all the possible use cases,also with the adapter classes, checkout from here [Androidbites|ViewBinding](https://chetangupta.net/viewbinding/) – Chetan Gupta Dec 07 '20 at 12:51

6 Answers6

63

You can use static bind method of ViewBinding to create binding from already existing layout. Add it as a property to viewholder:

class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
   val binding = ItemListBinding.bind(view)
}

then you can access all the views through the binding field, for example:

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    with(holder) {
          // TODO
          binding.tvMovieName.text = data[position].title
          binding.imageView.setDrawableImage(data[position].image)
    }
}
Pawel
  • 15,548
  • 3
  • 36
  • 36
32

Found another to resolve it

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val binding = ItemListBinding.inflate(LayoutInflater.from(context), parent, false)
        return MovieViewHolder(binding)
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        with(holder) {

            with(moviesData[position]) {
                binding.tvMovieName.text = title
            }
        }
    }
}

class MovieViewHolder(val binding: ItemMovieBinding) : RecyclerView.ViewHolder(binding.root)
DevAnuragGarg
  • 1,592
  • 2
  • 14
  • 18
18

Nothing really changes compared to how you'd normally do it.

class DataAdapter(
    private val context: Context,
    private val picasso: Picasso
) : RecyclerView.Adapter<DataAdapter.ViewHolder>() {
    private var dataList: List<Data> = Collections.emptyList()

    override fun getItemCount() = dataList.size

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = ViewHolder(
        LayoutInflater.from(context).inflate(R.layout.item_list, parent, false)
    )

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(dataList[position])
    }

    fun setData(dataList: List<Data>) {
        this.dataList = dataList
        notifyDataSetChanged()
    }

    class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        private val binding = ItemListBinding.bind(view)   

        fun bind(data: Data) {
            with(binding) {
                // TODO
                nameTV.text = data.name
                bannerImage.loadWithPicasso(picasso, data.imageUrl)
            }
        }
    }
}
EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
  • Hi Gobar! I see you did the binding in the scope of view holder instance, this means the binding variable would be released only when the view holder instance is destroyed? (does that happen on isRecycled callback? I don't know when view holders are destroyed..), if we do inflate in `onCreateViewHolder` and pass binding to view holder we don't have to be bothered by the same? I want to know which one should be preferred, what I have done in my article is right? https://chetangupta.net/viewbinding/ – Chetan Gupta Dec 07 '20 at 13:06
  • 1
    You can either pass the `view: View` directly and use `.bind` as long as it is stored as a `val`, or you can also pass `ItemListBinding` directly, their behavior would be equivalent. Sample here mostly intends to be "minimal intrusion", you can also use `.inflate` binding in adapter. I prefer `.bind()` in most cases but that's just me, i wouldn't say this is an "anti-pattern" – EpicPandaForce Dec 07 '20 at 13:41
  • 1
    so we only added `private val binding = ItemListBinding.bind(containerView)` and `with(binding){ // code }` .btw my code `fun bindItem(item: DatasItem, listener: (DatasItem) -> Unit) { }` using `LayoutContainer` i agree with you and we have similar/same code, nice. – Yogi Arif Widodo Oct 24 '21 at 01:12
4

I found the easiest way to use View Binding in the RecyclerView Adapter class

class MediaRecyclerViewAdapter: RecyclerView.Adapter<MediaRecyclerViewAdapter.MediaViewHolder>() {


lateinit var list: List<String>

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MediaViewHolder {
    val binding = CardviewItemsRecyclerviewBinding.inflate(LayoutInflater.from(parent.context),parent,false)
    return MediaViewHolder(binding)
}

override fun onBindViewHolder(holder: MediaViewHolder, position: Int) {

    holder.binding.mediaHeadingTextView.text = "Hello"

}

override fun getItemCount(): Int {
    return list.size
}

class MediaViewHolder(val binding: CardviewItemsRecyclerviewBinding) : RecyclerView.ViewHolder(binding.root) {

   }

}
Ghayas
  • 1,266
  • 10
  • 17
2

I found this the easiest:

class RvAdapter() :
    RecyclerView.Adapter<RvAdapter.RvViewHolder>() {
        
    inner class RvViewHolder(val binding: OtherRvItemBinding) :
    RecyclerView.ViewHolder(binding.root)
        
    // Initialise view binding here.
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RvViewHolder {
        val binding =
            OtherRvItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return RvViewHolder(binding)
    }
    
    override fun getItemCount(): Int {
        // TODO: Implement logic to return list size.
        return 0
    }
    
    // Bind view holder.
    override fun onBindViewHolder(holder: RvViewHolder, position: Int) {
        holder.binding.apply {
                // Reference your views from here.
                awesomeTv.text = "Hello World!"
        }
    }    
}
Gourav
  • 2,746
  • 5
  • 28
  • 45
0

I use this template that also integrates with click event:


class BannerAdapter(private val listener: OnCategoryClickListener) :
    ListAdapter<Banner, BannerAdapter.BannerViewHolder>(DiffCallBack()) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BannerViewHolder {
        val binding = ItemRecyclerviewCategoriesSearchBinding.inflate(
            LayoutInflater.from(parent.context),
            parent,
            false
        )
        return BannerViewHolder(binding)
    }

    override fun onBindViewHolder(holder: BannerViewHolder, position: Int) {
        val currentItem = getItem(position)
        holder.bind(currentItem)
    }

    inner class BannerViewHolder(private val binding: ItemRecyclerviewCategoriesSearchBinding) :
        RecyclerView.ViewHolder(binding.root) {

        init {
            binding.root.setOnClickListener {
                val position = absoluteAdapterPosition
                if (position != RecyclerView.NO_POSITION){
                    val banner = getItem(position)
                    listener.onCategoryClick(banner.link_type, banner.link)
                }
            }
        }

        fun bind(banner: Banner) {
            binding.apply {
                Glide.with(itemView.context)
                    .load(banner.media.url)
                    .centerCrop()
                    .transition(DrawableTransitionOptions.withCrossFade())
                    .error(R.drawable.ic_broken_image)
                    .into(imageCategory)
            }
        }
    }

    interface OnCategoryClickListener {
        fun onCategoryClick(type: String, link: String)
    }

    private class DiffCallBack : DiffUtil.ItemCallback<Banner>() {
        override fun areItemsTheSame(oldItem: Banner, newItem: Banner) =
            oldItem == newItem

        override fun areContentsTheSame(oldItem: Banner, newItem: Banner) =
            oldItem.media == newItem.media
    }
}
Kaaveh Mohamedi
  • 1,399
  • 1
  • 15
  • 36