Skip to content

Instantly share code, notes, and snippets.

@yogacp
Last active September 12, 2023 11:53
Show Gist options
  • Save yogacp/765136a364de1bf5e3e735bf8a69f930 to your computer and use it in GitHub Desktop.
Save yogacp/765136a364de1bf5e3e735bf8a69f930 to your computer and use it in GitHub Desktop.
Recyclerview that use Abstract Adapter and viewbinding
/**
* Create the Abstract Adapter and the ViewHolder that we need to hold the item view
*/
abstract class AbstractAdapter<T : ViewBinding, ITEM> constructor(
protected var itemList: List<ITEM>,
private val bindingClass: (LayoutInflater, ViewGroup, Boolean) -> T
) : RecyclerView.Adapter<AbstractAdapter.Holder>() {
var binding: T? = null
init {
update(itemList)
notifyDataSetChanged()
}
override fun getItemCount(): Int = itemList.size
override fun getItemViewType(position: Int): Int = position
@Suppress("UNCHECKED_CAST")
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
//inflate the bindingClass using ViewGroup binding delegation
val viewBinding = parent.viewBinding(bindingClass)
this.binding = viewBinding
val viewHolder = Holder(viewBinding.root)
val itemView = viewHolder.itemView
itemView.tag = viewHolder
itemView.setOnClickListener {
val adapterPosition = viewHolder.adapterPosition
if (adapterPosition != RecyclerView.NO_POSITION) {
onItemClick(itemView, adapterPosition)
}
}
return viewHolder
}
override fun onBindViewHolder(holder: Holder, position: Int) {
val item = itemList[position]
holder.itemView.bind(item)
}
override fun onViewRecycled(holder: Holder) {
super.onViewRecycled(holder)
onViewRecycled(holder.itemView)
}
private fun updateAdapterWithDiffResult(result: DiffUtil.DiffResult) {
result.dispatchUpdatesTo(this)
}
private fun calculateDiff(newItems: List<ITEM>): DiffUtil.DiffResult {
return DiffUtil.calculateDiff(DiffUtilCallback(itemList, newItems))
}
private fun update(items: List<ITEM>) {
updateAdapterWithDiffResult(calculateDiff(items))
}
private fun add(item: ITEM) {
itemList.toMutableList().add(item)
notifyItemInserted(itemList.size)
}
private fun remove(position: Int) {
itemList.toMutableList().removeAt(position)
notifyItemRemoved(position)
}
protected open fun View.bind(item: ITEM) {}
protected open fun onViewRecycled(itemView: View) {}
protected open fun onItemClick(itemView: View, position: Int) {}
class Holder(itemView: View) : RecyclerView.ViewHolder(itemView)
}
/**
* Create a DiffUtil callback
*/
internal class DiffUtilCallback<ITEM>(
private val oldItems: List<ITEM>,
private val newItems: List<ITEM>
): DiffUtil.Callback() {
override fun getOldListSize(): Int = oldItems.size
override fun getNewListSize(): Int = newItems.size
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return oldItems[oldItemPosition] == newItems[newItemPosition]
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return oldItems[oldItemPosition] == newItems[newItemPosition]
}
}
/**
* Create a General Adapter to handle the itemView setup and click
*/
class GeneralAdapter<T : ViewBinding, ITEM>(
items: List<ITEM>,
bindingClass: (LayoutInflater, ViewGroup, Boolean) -> T,
private val bindHolder: View.(T?, ITEM) -> Unit
) : AbstractAdapter<T, ITEM>(items, bindingClass) {
private var itemClick: View.(ITEM) -> Unit = {}
var viewBinding: T? = null
constructor(
items: List<ITEM>,
bindingClass: (LayoutInflater, ViewGroup, Boolean) -> T,
bindHolder: View.(T?, ITEM) -> Unit,
itemViewClick: View.(ITEM) -> Unit = {}
) : this(items, bindingClass, bindHolder) {
this.itemClick = itemViewClick
}
override fun onBindViewHolder(holder: Holder, position: Int) {
super.onBindViewHolder(holder, position)
if (position == holder.adapterPosition) {
this.viewBinding = binding
holder.itemView.bindHolder(binding, itemList[position])
}
}
override fun onItemClick(itemView: View, position: Int) {
itemView.itemClick(itemList[position])
}
}
/**
* And here how we use it, Just call the recyclerview binding and call .setup() and include
*/
binding.recyclerview.setup(
listData,
ItemSampleBinding::inflate,
{ binding, data ->
binding?.tvTitle?.text = data.name
binding?.btnSetup?.setOnClickListener {
showAlert(data.description)
}
}
)
/**
* Create the ViewGroup binding delegation
*/
inline fun <T : ViewBinding> ViewGroup.viewBinding(binding: (LayoutInflater, ViewGroup, Boolean) -> T): T {
return binding(LayoutInflater.from(context), this, false)
}
/**
* Create an extension function to RecyclerView
* And create function setup() that implement the GeneralAdapter and its viewBinding.
*/
fun <T : ViewBinding, ITEM> RecyclerView.setup(
items: List<ITEM>,
bindingClass: (LayoutInflater, ViewGroup, Boolean) -> T,
bindHolder: View.(T?, ITEM) -> Unit,
itemClick: View.(ITEM) -> Unit = {},
manager: RecyclerView.LayoutManager = LinearLayoutManager(this.context)
): GeneralAdapter<T, ITEM> {
val generalAdapter by lazy {
GeneralAdapter(items, bindingClass,
{ binding: T?, item: ITEM ->
bindHolder(binding, item)
}, {
itemClick(it)
}
)
}
layoutManager = manager
adapter = generalAdapter
(adapter as GeneralAdapter<*, *>).notifyDataSetChanged()
return generalAdapter
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment