Skip to content

Instantly share code, notes, and snippets.

@AAverin
Created April 11, 2023 18:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AAverin/de25adbddb2c121a12128d59e14a675e to your computer and use it in GitHub Desktop.
Save AAverin/de25adbddb2c121a12128d59e14a675e to your computer and use it in GitHub Desktop.
Component-based adapter
class CmpntViewHolder<T>(val cmpnt: Cmpnt<T>, view: View) : RecyclerView.ViewHolder(view)
abstract class Cmpnt<T>(@field:LayoutRes private val cmpntLayoutId: Int) {
open fun setData(data: T) {}
protected open fun onViewCreated(view: View) {}
fun createView(inflater: LayoutInflater, parent: ViewGroup?): View {
val view = inflater.inflate(cmpntLayoutId, parent, false)
onViewCreated(view)
return view
}
fun onVisible() {}
fun onHidden() {}
}
class CmpntrRecyclerAdapter(private val layoutInflater: LayoutInflater) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var items: List<Any> = listOf()
fun setItems(newItems: List<Any>): Boolean {
val oldItems = items
items = newItems as MutableList<Any>
DiffUtil.calculateDiff(SimpleDiffUtilCallback(newItems, oldItems)).dispatchUpdatesTo(this)
return oldItems != newItems
}
//public to be accessible by `register` inline function
var cmpntProviders = LinkedHashMap<Class<out Any>, () -> Cmpnt<*>>()
inline fun <reified T : Any> register(noinline cmpntProvider: () -> Cmpnt<T>) {
cmpntProviders[T::class.java] = cmpntProvider
}
override fun getItemViewType(position: Int) = cmpntProviders.keys.indexOf(items[position].javaClass)
override fun getItemId(position: Int): Long = position.toLong()
override fun getItemCount() = items.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val cmpnt = cmpntProviders.values.elementAt(viewType)()
return CmpntViewHolder(cmpnt, cmpnt.createView(layoutInflater, parent))
}
@Suppress("UNCHECKED_CAST")
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
(holder as CmpntViewHolder<Any>).cmpnt.setData(items[position])
}
@Suppress("UNCHECKED_CAST")
override fun onViewAttachedToWindow(holder: RecyclerView.ViewHolder) {
super.onViewAttachedToWindow(holder)
(holder as CmpntViewHolder<Any>).cmpnt.onVisible()
}
@Suppress("UNCHECKED_CAST")
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
super.onViewDetachedFromWindow(holder)
(holder as CmpntViewHolder<Any>).cmpnt.onHidden()
}
}
//as an example how to use Cmpnt
//first setup an adapter somewhere and register components on it
class CmpntrLib @Inject constructor(
private val gleamCmpntProvider: Provider<GleamCmpnt>,
) {
fun setup(recycler: RecyclerView, adapter: CmpntrRecyclerAdapter) {
recycler.adapter = adapter
adapter.apply {
register { gleamCmpntProvider.get() }
}
}
}
class GleamCmpnt @Inject constructor(
private val timeHelper: TimeHelper,
private val analytics: FirebaseAnalytics
) : Cmpnt<GleamEntry>(R.layout.cmpnt_gleam_long) {
lateinit var binding: CmpntGleamLongBinding
override fun setData(data: GleamEntry) {
super.setData(data)
binding.data = data
binding.executePendingBindings()
}
override fun onViewCreated(view: View) {
super.onViewCreated(view)
binding = DataBindingUtil.bind(view)!!
binding.cmpnt = this
binding.timeHelper = timeHelper
}
fun click(view: View) {
//...
}
fun clickSource(view: View) {
// ...
}
}
class SimpleDiffUtilCallback<T>(
protected val newItems: List<T>,
protected val oldItems: List<T>
) : DiffUtil.Callback() {
override fun getOldListSize() = oldItems.size
override fun getNewListSize() = newItems.size
override fun areItemsTheSame(oldPosition: Int, newPosition: Int) = newItems[newPosition] == oldItems[oldPosition]
override fun areContentsTheSame(oldPosition: Int, newPosition: Int) = newItems[newPosition] == oldItems[oldPosition]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment