Skip to content

Instantly share code, notes, and snippets.

@AlecKazakova
Created June 12, 2019 14:45
Show Gist options
  • Save AlecKazakova/2c5ab7f7f58a5db703d1a7cbd0e1cf9d to your computer and use it in GitHub Desktop.
Save AlecKazakova/2c5ab7f7f58a5db703d1a7cbd0e1cf9d to your computer and use it in GitHub Desktop.
import android.os.Looper
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.Adapter
import androidx.recyclerview.widget.RecyclerView.NO_POSITION
import androidx.recyclerview.widget.RecyclerView.ViewHolder
class ComposableAdapter: Adapter<ViewHolder>() {
private val adapterForViewType = LinkedHashMap<Int, Adapter<ViewHolder>>()
private val observers = ArrayList<Observer>()
private var adapters: List<Adapter<ViewHolder>> = emptyList()
set(value) {
field.forEachIndexed { index, adapter ->
adapter.unregisterAdapterDataObserver(observers[index])
}
observers.clear()
adapterForViewType.clear()
value.fold(0, { priorCount, adapter ->
val observer = Observer(priorCount)
adapter.registerAdapterDataObserver(observer)
observers.add(observer)
return@fold priorCount + adapter.itemCount
})
val oldValue = field
field = value
DiffUtil.calculateDiff(object : DiffUtil.Callback() {
override fun areItemsTheSame(
oldItemPosition: Int,
newItemPosition: Int
): Boolean {
val (oldPrior, oldAdapter) = oldValue.adapterForPosition(oldItemPosition)
val (newPrior, newAdapter) = value.adapterForPosition(newItemPosition)
val oldPosition = oldItemPosition - oldPrior
val newPosition = newItemPosition - newPrior
if (oldAdapter.hasStableIds() && newAdapter.hasStableIds()) {
return (oldAdapter.getItemViewType(oldPosition) == newAdapter.getItemViewType(newPosition)
&& (oldAdapter.getItemId(oldPosition) == newAdapter.getItemId(newPosition)))
}
return (oldAdapter == newAdapter) && (oldPosition == newPosition)
}
override fun getOldListSize() = oldValue.sumBy { it.itemCount }
override fun getNewListSize() = value.sumBy { it.itemCount }
override fun areContentsTheSame(
oldItemPosition: Int,
newItemPosition: Int
) = areItemsTheSame(oldItemPosition, newItemPosition)
}).dispatchUpdatesTo(this)
}
@Suppress("UNCHECKED_CAST")
fun setData(adapters: List<Adapter<out ViewHolder>>) {
this.adapters = adapters as List<Adapter<ViewHolder>>
}
private fun List<RecyclerView.Adapter<*>>.adapterForPosition(position: Int): InnerAdapter {
fold(0, { total, adapter ->
if (total + adapter.itemCount > position) return InnerAdapter(total, adapter)
total + adapter.itemCount
})
throw IllegalStateException("No adapter for position $position, itemCount: $itemCount, adapters:" +
" ${adapters.joinToString { "${it.javaClass.canonicalName}: ${it.itemCount} items" }}")
}
fun adapterForPosition(position: Int) = adapters.adapterForPosition(position)
override fun getItemViewType(position: Int): Int {
adapters.fold(0, { total, adapter ->
if (total + adapter.itemCount > position) {
val viewType = adapter.getItemViewType(position - total)
adapterForViewType[viewType] = adapter
return viewType
}
total + adapter.itemCount
})
throw IllegalStateException("No viewtype for position $position, itemCount: $itemCount, adapters:" +
" ${adapters.joinToString { "${it.javaClass.canonicalName}: ${it.itemCount} items" }}")
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): ViewHolder {
return adapterForViewType[viewType]?.onCreateViewHolder(parent, viewType)
?: throw NullPointerException("No adapter for view type $viewType")
}
override fun getItemCount(): Int {
return adapters.sumBy { it.itemCount }
}
override fun getItemId(position: Int): Long {
if (!hasStableIds()) {
return super.getItemId(position)
}
adapters.fold(0, { total, adapter ->
if (total + adapter.itemCount > position) {
return adapter.getItemId(position - total)
}
total + adapter.itemCount
})
throw IllegalStateException()
}
override fun onBindViewHolder(
holder: ViewHolder,
position: Int
) {
adapters.fold(0, { total, adapter ->
if (total + adapter.itemCount > position) {
adapter.onBindViewHolder(holder, position - total)
return
}
total + adapter.itemCount
})
throw IllegalStateException()
}
private inner class Observer(private var priorCount: Int) : RecyclerView.AdapterDataObserver() {
private fun checkLooper() {
if (Looper.myLooper() != Looper.getMainLooper()) {
throw IllegalStateException("Can only notify on main thread")
}
}
override fun onChanged() {
checkLooper()
notifyDataSetChanged()
}
override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {
checkLooper()
notifyItemRangeChanged(positionStart + priorCount, itemCount)
}
override fun onItemRangeChanged(positionStart: Int, itemCount: Int, payload: Any?) {
checkLooper()
notifyItemRangeChanged(positionStart + priorCount, itemCount, payload)
}
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
checkLooper()
observers.drop(observers.indexOf(this) + 1).forEach {
it.priorCount += itemCount
}
notifyItemRangeInserted(positionStart + priorCount, itemCount)
}
override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {
checkLooper()
notifyDataSetChanged()
}
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
checkLooper()
observers.drop(observers.indexOf(this) + 1).forEach {
it.priorCount -= itemCount
}
notifyItemRangeRemoved(positionStart + priorCount, itemCount)
}
}
data class InnerAdapter(
val preceedingItems: Int,
val adapter: RecyclerView.Adapter<*>
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment