Created
December 10, 2017 11:35
-
-
Save halcyonmobiledev/0c8d5dae9171dce2a08054f4b9575944 to your computer and use it in GitHub Desktop.
Abstract RecyclerView adapter which uses DataBinding & MVVM pattern to bind data to each view item
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (c) 2017 Halcyon Mobile | |
* http://www.halcyonmobile.com | |
* All rights reserved. | |
*/ | |
import android.databinding.DataBindingUtil | |
import android.databinding.OnRebindCallback | |
import android.databinding.ViewDataBinding | |
import android.support.annotation.CallSuper | |
import android.support.annotation.LayoutRes | |
import android.support.v7.widget.RecyclerView | |
import android.view.LayoutInflater | |
import android.view.ViewGroup | |
abstract class BindingViewModelAdapter<VB : ViewDataBinding, VM : Any> : RecyclerView.Adapter<BindingViewModelAdapter.BindingViewHolder<VB, VM>>() { | |
private var recyclerView: RecyclerView? = null | |
private var itemClickListener: ((position: Int) -> Unit)? = null | |
private val rebindCallback: OnRebindCallback<VB> | |
init { | |
rebindCallback = object : OnRebindCallback<VB>() { | |
override fun onPreBind(binding: VB): Boolean { | |
return recyclerView?.let { recyclerView -> | |
val childAdapterPosition = recyclerView.getChildAdapterPosition(binding.root) | |
(recyclerView.isComputingLayout || childAdapterPosition == RecyclerView.NO_POSITION).also { | |
if (!it) { | |
notifyItemChanged(childAdapterPosition, DB_PAYLOAD) | |
} | |
} | |
} ?: true | |
} | |
} | |
} | |
@LayoutRes protected abstract fun getItemLayoutId(position: Int): Int | |
protected abstract fun bindItem(holder: BindingViewHolder<VB, VM>, position: Int, payloads: List<Any>) | |
protected abstract fun createViewModel(@LayoutRes viewType: Int): VM | |
fun setItemClickListener(itemClickListener: (position: Int) -> Unit) { | |
this.itemClickListener = itemClickListener | |
} | |
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { | |
this.recyclerView = recyclerView | |
} | |
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView?) { | |
this.recyclerView = null | |
} | |
override fun onBindViewHolder(holder: BindingViewHolder<VB, VM>, position: Int, payloads: MutableList<Any>) { | |
// when a VH is rebound to the same item, we don't have to call the setters | |
if (payloads.isEmpty() || payloads.any { it !== DB_PAYLOAD }) { | |
bindItem(holder, position, payloads) | |
} | |
holder.binding.executePendingBindings() | |
} | |
override final fun onBindViewHolder(holder: BindingViewHolder<VB, VM>, position: Int) { | |
throw IllegalArgumentException("Just overridden to make final.") | |
} | |
@CallSuper override fun onCreateViewHolder(parent: ViewGroup, @LayoutRes viewType: Int): BindingViewHolder<VB, VM> { | |
val viewHolder = BindingViewHolder.create<VB, VM>(parent, viewType, createViewModel(viewType)) | |
viewHolder.binding.addOnRebindCallback(rebindCallback) | |
viewHolder.setItemClickListener(itemClickListener) | |
return viewHolder | |
} | |
override final fun getItemViewType(position: Int) = getItemLayoutId(position) | |
companion object { | |
private val DB_PAYLOAD = Any() | |
} | |
class BindingViewHolder<out VB : ViewDataBinding, out VM : Any> | |
private constructor(val binding: VB, val viewModel: VM) : RecyclerView.ViewHolder(binding.root) { | |
init { | |
binding.setVariable(BR.viewModel, viewModel) | |
} | |
fun setItemClickListener(itemClickListener: ((position: Int) -> Unit)?) { | |
binding.root.setOnClickListener { | |
if (adapterPosition != RecyclerView.NO_POSITION) { | |
itemClickListener?.invoke(adapterPosition) | |
} | |
} | |
} | |
companion object { | |
@JvmStatic | |
fun <VB : ViewDataBinding, VM : Any> create(parent: ViewGroup, @LayoutRes layoutId: Int, viewModel: VM): BindingViewHolder<VB, VM> { | |
val binding: VB = DataBindingUtil.inflate(LayoutInflater.from(parent.context), layoutId, parent, false) | |
return BindingViewHolder(binding, viewModel) | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment