Generic RecyclerView Adapter with ViewBinding

799 views Asked by At

I'm attempting to implement a generic RV Adapter, mostly inspired by THIS COMMENT:

The code:

class RVAdapter <T: Any, VB: ViewBinding>(
  private var dataSet: List<T>,
  private val bindingInterface: RVBinding<T, VB>
  ) : ListAdapter<T, RVAdapter<T, VB>.RVHolder>(DiffCallback<T>()) {

  inner class RVHolder(private val binding: ViewBinding): RecyclerView.ViewHolder(binding.root) {
    fun<T: Any, VB: ViewBinding> bind(item: T, bindingInterface: RVBinding<T, VB>) {
      bindingInterface.bind(item, (binding as VB)) //3: unchecked cast
    }
  }


  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RVHolder {
    val binding = (VB as ViewBinding).inflate() //1: how do you inflate from generic VB?

    return RVHolder(binding)
  }

  override fun onBindViewHolder(holder: RVHolder, position: Int) {
    holder.bind(getItem(position), bindingInterface)
  }

  override fun getItemCount() = dataSet.size
}


private class DiffCallback<T: Any>: DiffUtil.ItemCallback<T>() {
  //2: how do you implement the following two methods?

  override fun areItemsTheSame(oldItem: T, newItem: T): Boolean {
    TODO("Not yet implemented")
  }

  override fun areContentsTheSame(oldItem: T, newItem: T): Boolean {
    return oldItem == newItem
  }
}


interface RVBinding<T: Any, VB: ViewBinding> {
  fun bind(item: T, binding: VB)
}

I'm facing two main problems:

  1. how do I inflate the binding in onCreateViewHolder()? Normally, I use SpecificBinding.inflate() but that doesn't seem to work with a generic view binding.
  2. how would I implement the DiffUtil portion in a generic way? Sure, I could compare old.toString == new.toString but that's not generic. Also oldItem == newItem doesn't have equals() implemented.

So how would I solve these problems in a way to make the Adapter as generic as possible? And any other suggestions would be welcome.

1

There are 1 answers

0
ΓDΛ On

For example, you can use the generic structure below.

abstract class AbstractViewBindingAdapter<T, VH : RecyclerView.ViewHolder, VB : ViewBinding>(
        private val viewHolder: (binding: VB) -> VH,
        private val bindingInflater: (LayoutInflater, ViewGroup, Boolean) -> VB,
        areItemsTheSameCallback: (old: T, new: T) -> Boolean? = { _, _ -> null },
        areContentsTheSameCallback: (old: T, new: T) -> Boolean? = { _, _ -> null },
        private val onCreateBinding: (holder: VH) -> Unit = {}

) :
        ListAdapter<T, VH>(GenericDiffUtil(areItemsTheSameCallback, areContentsTheSameCallback)) {
    abstract fun bindItems(item: T, holder: VH, position: Int, itemCount: Int)

    var forItemClickListener: ((position: Int, item: T, view: View) -> Unit)? = null
    var onLongClickListener: ((position: Int, item: T, view: View) -> Unit)? = null

    override fun onBindViewHolder(holder: VH, position: Int) {
        val item: T = getItem(holder.bindingAdapterPosition)
        bindItems(item, holder, position, itemCount)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
        val binding = bindingInflater.invoke(LayoutInflater.from(parent.context), parent, false)
        val holder = setViewHolder(binding)
        onCreateBinding(holder)

        holder.itemView.setOnClickListenerCooldown {
            if (holder.bindingAdapterPosition != RecyclerView.NO_POSITION)
                forItemClickListener?.invoke(holder.bindingAdapterPosition, getItem(holder.bindingAdapterPosition), it)
        }
        holder.itemView.setOnLongClickListener {
            if (holder.bindingAdapterPosition != RecyclerView.NO_POSITION)
                onLongClickListener?.invoke(holder.bindingAdapterPosition, getItem(holder.bindingAdapterPosition), it)
            true
        }
        return holder
    }

    @Suppress("UNCHECKED_CAST")
    private fun setViewHolder(binding: ViewBinding): VH = viewHolder(binding as VB)
}

GenericDiffUtil.kt

class GenericDiffUtil<T>(private val areItemsTheSameCallback: (old: T, new: T) -> Boolean?,
                         private val areContentsTheSameCallback: (old: T, new: T) -> Boolean?) : DiffUtil.ItemCallback<T>() {

    override fun areItemsTheSame(oldItem: T, newItem: T): Boolean = areItemsTheSameCallback(oldItem, newItem) ?: newItem == oldItem

    @SuppressLint("DiffUtilEquals")
    override fun areContentsTheSame(oldItem: T, newItem: T): Boolean = areContentsTheSameCallback(oldItem, newItem) ?: newItem == oldItem

}

fun <T> diffUtilDSL(areItemsTheSameCallback: (old: T, new: T) -> Boolean? = { _, _ -> null },
                    areContentsTheSameCallback: (old: T, new: T) -> Boolean? = { _, _ -> null }) = GenericDiffUtil(areItemsTheSameCallback, areContentsTheSameCallback)