Skip to content
This repository has been archived by the owner on Jul 16, 2024. It is now read-only.

Commit

Permalink
Simplify base RV adapters using delegates (#630)
Browse files Browse the repository at this point in the history
* Add delegate for BaseRvDiffAdapter

* Apply delegate for BaseRvAdapter and BaseRvPagingAdapter

* Fix list getting

* Replace TODO

* Remove casting

* Remove extra abstract functions

* Fix conflicts

* Move @Suppress("NotifyDataSetChanged")

* Tweak IMutableRvAdapterDelegate

* Better error message

* New IRvBindingAdapter

This reverts commit e4b069f.

* Rename IRvBindingAdapter

* Tweak RvAdapterDelegate

* Tweak ViewTypeDelegate

* Remove outdated TooManyFunctions

* Cleanups

* Use toPersistentList

* Rename

* Inline

* Tweak message

* Still use toImmutableList

* Add operator

* Cleanup

* Move refresh functions into delegate

* Tweak

* Move delegates into base interfaces

* Don't open BindingViewHolder

* Tweaks

* Suppress DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE

* Tweak

* Remove redundant inline functions

* Use super.list

* Rearrange

* Move transformation into IMutableRvAdapter.Impl

* Better naming
  • Loading branch information
Goooler authored Jun 26, 2024
1 parent 343b62b commit 90c49bf
Show file tree
Hide file tree
Showing 12 changed files with 283 additions and 367 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package io.goooler.demoapp.adapter.rv.core

import android.view.ViewGroup
import androidx.annotation.LayoutRes
import androidx.recyclerview.widget.RecyclerView
import kotlinx.collections.immutable.toImmutableList

/**
* Created on 2020/10/22.
Expand All @@ -14,62 +11,25 @@ import kotlinx.collections.immutable.toImmutableList
* @version 1.0.0
* @since 1.0.0
*/
@Suppress("NotifyDataSetChanged", "TooManyFunctions")
abstract class BaseRvAdapter<M : IVhModelType> :
RecyclerView.Adapter<BindingViewHolder>(),
IMutableRvAdapter<M> {

private val helper by lazy(LazyThreadSafetyMode.NONE) { RvAdapterHelper(this) }

override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
super.onAttachedToRecyclerView(recyclerView)
helper.onAttachedToRecyclerView(recyclerView)
}

override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
super.onDetachedFromRecyclerView(recyclerView)
helper.onDetachedFromRecyclerView(recyclerView)
}

override fun onCreateViewHolder(parent: ViewGroup, @LayoutRes viewType: Int): BindingViewHolder =
helper.onCreateViewHolder(parent, viewType)

override fun onBindViewHolder(holder: BindingViewHolder, position: Int) {
helper.onBindViewHolder(holder, position)
}

override fun onBindViewHolder(
holder: BindingViewHolder,
position: Int,
payloads: List<Any>,
) {
helper.onBindViewHolder(holder, position, payloads)
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
abstract class BaseRvAdapter<M : IVhModelType> private constructor(
private val delegate: IMutableRvAdapter.Impl<M, BaseRvAdapter<M>>,
) : RecyclerView.Adapter<BindingViewHolder>(),
IRvBinding<M>,
IMutableRvAdapter<M> by delegate {

constructor() : this(IMutableRvAdapter.Impl()) {
@Suppress("LeakingThis")
delegate.adapter = this
}

@LayoutRes
override fun getItemViewType(position: Int): Int =
helper.list[position].viewType

override fun getItemCount(): Int = helper.list.size

override operator fun get(position: Int): M = helper.list[position]

override var list: List<M>
get() = helper.list.toImmutableList()
get() = delegate.list
set(value) {
helper.list = value
delegate.list = value
@Suppress("NotifyDataSetChanged")
notifyDataSetChanged()
}

override fun refreshItems(items: List<M>) {
helper.refreshItems(items, ::notifyItemChanged)
}

override fun removeItem(index: Int) {
helper.removeItem(index, ::notifyItemRemoved)
}

override fun removeItem(item: M) {
helper.removeItem(item, ::notifyItemRemoved)
}
override fun getItemCount(): Int = delegate.list.size
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package io.goooler.demoapp.adapter.rv.core

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.annotation.LayoutRes
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.RecyclerView

Expand All @@ -12,4 +16,17 @@ import androidx.recyclerview.widget.RecyclerView
* @version 1.0.0
* @since 1.0.0
*/
open class BindingViewHolder(val binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root)
class BindingViewHolder(val binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root) {

companion object {
fun create(parent: ViewGroup, @LayoutRes viewType: Int): BindingViewHolder {
val binding = DataBindingUtil.inflate<ViewDataBinding>(
LayoutInflater.from(parent.context),
viewType,
parent,
false,
)
return BindingViewHolder(binding)
}
}
}
182 changes: 162 additions & 20 deletions adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/core/IRvAdapter.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
package io.goooler.demoapp.adapter.rv.core

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.annotation.LayoutRes
import androidx.databinding.BindingAdapter
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import io.goooler.demoapp.adapter.rv.core.ISpanSize.Companion.SPAN_SIZE_FULL
import kotlinx.collections.immutable.toImmutableList

/**
* Keep the same signature as [RecyclerView.Adapter].
*
* Workaround for [KT-21955](https://youtrack.jetbrains.com/issue/KT-21955).
*/
internal interface RecyclerViewAdapter<VH : RecyclerView.ViewHolder> {
fun onAttachedToRecyclerView(recyclerView: RecyclerView)
fun onDetachedFromRecyclerView(recyclerView: RecyclerView)
fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH
fun onBindViewHolder(holder: BindingViewHolder, position: Int)
fun onBindViewHolder(holder: BindingViewHolder, position: Int, payloads: List<Any>)
fun getItemViewType(position: Int): Int
}

/**
* Created on 2020/10/21.
Expand All @@ -17,13 +33,18 @@ import androidx.recyclerview.widget.RecyclerView
* @version 1.0.0
* @since 1.0.0
*/
internal interface IRvAdapter<M : IVhModelType> {
internal interface IRvAdapter<M : IVhModelType> : RecyclerViewAdapter<BindingViewHolder> {

/**
* Get data list.
*/
val list: List<M>

/**
* Get item by position.
*/
operator fun get(position: Int): M?

/**
* What to do when creating the viewHolder for all.
*/
Expand All @@ -34,28 +55,98 @@ internal interface IRvAdapter<M : IVhModelType> {
*/
fun onBindVHForAll(binding: ViewDataBinding, model: M, payloads: List<Any>)

/**
* Create BaseViewHolder.
*/
fun createVH(parent: ViewGroup, @LayoutRes viewType: Int): BindingViewHolder {
val binding = DataBindingUtil.inflate<ViewDataBinding>(
LayoutInflater.from(parent.context),
viewType,
parent,
false,
)
return BindingViewHolder(binding)
}

/**
* Init ViewTypeDelegateManager. You can add VTDs.
*/
fun initManager(manager: ViewTypeDelegateManager<M>) {}

/**
* Get item by position.
*/
operator fun get(position: Int): M?
@Suppress("TooManyFunctions")
open class Impl<M : IVhModelType, AP> : IRvAdapter<M>
where AP : IRvAdapter<M>,
AP : IRvBinding<M>,
AP : RecyclerView.Adapter<BindingViewHolder> {

private val ivdManager = ViewTypeDelegateManager<M>()

@Suppress("PropertyName", "VariableNaming", "ktlint:standard:backing-property-naming")
protected val _list = mutableListOf<M>()

lateinit var adapter: AP

override val list: List<M> get() = _list.toImmutableList()

override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
initManager(ivdManager)
fixSpanSize(recyclerView)
}

override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
ivdManager.clear()
recyclerView.adapter = null
}

override operator fun get(position: Int): M = _list.getOrElse(position) {
// Override get in adapters as a fallback.
adapter[position] ?: error("No such a element in position $position in adapter $adapter.")
}

@LayoutRes
override fun getItemViewType(position: Int): Int = get(position).viewType

override fun onCreateViewHolder(
parent: ViewGroup,
@LayoutRes viewType: Int,
): BindingViewHolder = BindingViewHolder.create(parent, viewType).also {
adapter.onCreateVHForAll(it.binding)
ivdManager.onCreateVH(it.binding, viewType)
}

override fun onBindViewHolder(holder: BindingViewHolder, position: Int) {
onBindViewHolder(holder, position, emptyList())
}

override fun onBindViewHolder(holder: BindingViewHolder, position: Int, payloads: List<Any>) {
get(position).let {
setFullSpan(holder, it)
adapter.onBindVHForAll(holder.binding, it, payloads)
ivdManager.onBindVH(holder.binding, it, payloads)
holder.binding.executePendingBindings()
}
}

override fun onCreateVHForAll(binding: ViewDataBinding) {
adapter.onCreateVH(binding)
}

override fun onBindVHForAll(binding: ViewDataBinding, model: M, payloads: List<Any>) {
adapter.onBindVH(binding, model, payloads)
}

/**
* Fix span size when recyclerView's layoutManager is [GridLayoutManager].
*/
private fun fixSpanSize(recyclerView: RecyclerView) {
(recyclerView.layoutManager as? GridLayoutManager)?.let {
it.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
(get(position) as? ISpanSize)?.spanSize?.let { size ->
return if (size == SPAN_SIZE_FULL) it.spanCount else size
}
return it.spanCount
}
}
}
}

/**
* Set full span when recyclerView's layoutManager is [StaggeredGridLayoutManager].
*/
private fun setFullSpan(holder: RecyclerView.ViewHolder, item: M) {
(holder.itemView.layoutParams as? StaggeredGridLayoutManager.LayoutParams)?.let {
it.isFullSpan = (item as? ISpanSize)?.spanSize == SPAN_SIZE_FULL
}
}
}
}

internal interface IMutableRvAdapter<M : IVhModelType> : IRvAdapter<M> {
Expand All @@ -73,6 +164,57 @@ internal interface IMutableRvAdapter<M : IVhModelType> : IRvAdapter<M> {
fun removeItem(index: Int)

fun removeItem(item: M)

class Impl<M : IVhModelType, AP> :
IRvAdapter.Impl<M, AP>(),
IMutableRvAdapter<M>
where AP : IRvAdapter<M>,
AP : IRvBinding<M>,
AP : RecyclerView.Adapter<BindingViewHolder> {

override var list: List<M>
get() = super.list
set(value) {
_list.clear()
_list.addAll(flat(value))
}

override fun refreshItems(items: List<M>) {
flat(items).forEach {
if (it in _list) {
adapter.notifyItemChanged(_list.indexOf(it))
}
}
}

override fun removeItem(index: Int) {
_list.removeAt(index)
adapter::notifyItemRemoved
}

override fun removeItem(item: M) {
_list.indexOf(item).takeIf { it != -1 }?.let {
removeItem(it)
adapter::notifyItemRemoved
}
}

private fun flat(original: List<M>): List<M> {
val result = mutableListOf<M>()
original.forEach { findLeaf(it, result) }
return result
}

private fun findLeaf(model: M, list: MutableList<M>) {
if (model is IVhModelWrapper<*>) {
if (model.viewType != -1) list += model
@Suppress("UNCHECKED_CAST")
model.subList.forEach { findLeaf(it as M, list) }
} else {
list += model
}
}
}
}

@BindingAdapter("binding_rv_dataList")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.goooler.demoapp.adapter.rv.core

import androidx.databinding.ViewDataBinding

interface IRvBinding<M : IVhModelType> {

/**
* What to do when creating the viewHolder.
*
* @param binding ViewDataBinding
*/
fun onCreateVH(binding: ViewDataBinding)

/**
* What to do when binding the viewHolder.
*
* @param binding ViewDataBinding
* @param model model
*/
fun onBindVH(binding: ViewDataBinding, model: M, payloads: List<Any>)
}
Loading

0 comments on commit 90c49bf

Please sign in to comment.