From 90c49bfe0520e9c555df83a065173a021d9a4e4a Mon Sep 17 00:00:00 2001 From: Zongle Wang Date: Tue, 25 Jun 2024 22:26:42 -0400 Subject: [PATCH] Simplify base RV adapters using delegates (#630) * 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 e4b069fef681b792991b32084a542b6f4ceb6a05. * 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 --- .../demoapp/adapter/rv/core/BaseRvAdapter.kt | 68 ++----- .../adapter/rv/core/BindingViewHolder.kt | 19 +- .../demoapp/adapter/rv/core/IRvAdapter.kt | 182 ++++++++++++++++-- .../demoapp/adapter/rv/core/IRvBinding.kt | 21 ++ .../adapter/rv/core/RvAdapterHelper.kt | 142 -------------- .../adapter/rv/core/ViewTypeDelegate.kt | 29 +-- .../rv/core/ViewTypeDelegateManager.kt | 6 +- .../adapter/rv/diff/BaseRvDiffAdapter.kt | 84 ++------ .../diff/{DiffCallBack.kt => DiffCallback.kt} | 2 +- .../adapter/rv/paging/BaseRvPagingAdapter.kt | 85 ++++---- .../main/ui/adapter/MainPagingRvAdapter.kt | 4 +- .../main/ui/adapter/MainSrlRvAdapter.kt | 8 +- 12 files changed, 283 insertions(+), 367 deletions(-) create mode 100644 adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/core/IRvBinding.kt delete mode 100644 adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/core/RvAdapterHelper.kt rename adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/diff/{DiffCallBack.kt => DiffCallback.kt} (88%) diff --git a/adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/core/BaseRvAdapter.kt b/adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/core/BaseRvAdapter.kt index a0f6280c9..9cebb9e9f 100644 --- a/adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/core/BaseRvAdapter.kt +++ b/adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/core/BaseRvAdapter.kt @@ -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. @@ -14,62 +11,25 @@ import kotlinx.collections.immutable.toImmutableList * @version 1.0.0 * @since 1.0.0 */ -@Suppress("NotifyDataSetChanged", "TooManyFunctions") -abstract class BaseRvAdapter : - RecyclerView.Adapter(), - IMutableRvAdapter { - - 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, - ) { - helper.onBindViewHolder(holder, position, payloads) +@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE") +abstract class BaseRvAdapter private constructor( + private val delegate: IMutableRvAdapter.Impl>, +) : RecyclerView.Adapter(), + IRvBinding, + IMutableRvAdapter 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 - get() = helper.list.toImmutableList() + get() = delegate.list set(value) { - helper.list = value + delegate.list = value + @Suppress("NotifyDataSetChanged") notifyDataSetChanged() } - override fun refreshItems(items: List) { - 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 } diff --git a/adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/core/BindingViewHolder.kt b/adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/core/BindingViewHolder.kt index 991c52bdf..a1980efd7 100644 --- a/adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/core/BindingViewHolder.kt +++ b/adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/core/BindingViewHolder.kt @@ -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 @@ -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( + LayoutInflater.from(parent.context), + viewType, + parent, + false, + ) + return BindingViewHolder(binding) + } + } +} diff --git a/adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/core/IRvAdapter.kt b/adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/core/IRvAdapter.kt index 758b8f4ee..ce4b9a054 100644 --- a/adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/core/IRvAdapter.kt +++ b/adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/core/IRvAdapter.kt @@ -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 { + 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) + fun getItemViewType(position: Int): Int +} /** * Created on 2020/10/21. @@ -17,13 +33,18 @@ import androidx.recyclerview.widget.RecyclerView * @version 1.0.0 * @since 1.0.0 */ -internal interface IRvAdapter { +internal interface IRvAdapter : RecyclerViewAdapter { /** * Get data list. */ val list: List + /** + * Get item by position. + */ + operator fun get(position: Int): M? + /** * What to do when creating the viewHolder for all. */ @@ -34,28 +55,98 @@ internal interface IRvAdapter { */ fun onBindVHForAll(binding: ViewDataBinding, model: M, payloads: List) - /** - * Create BaseViewHolder. - */ - fun createVH(parent: ViewGroup, @LayoutRes viewType: Int): BindingViewHolder { - val binding = DataBindingUtil.inflate( - LayoutInflater.from(parent.context), - viewType, - parent, - false, - ) - return BindingViewHolder(binding) - } - /** * Init ViewTypeDelegateManager. You can add VTDs. */ fun initManager(manager: ViewTypeDelegateManager) {} - /** - * Get item by position. - */ - operator fun get(position: Int): M? + @Suppress("TooManyFunctions") + open class Impl : IRvAdapter + where AP : IRvAdapter, + AP : IRvBinding, + AP : RecyclerView.Adapter { + + private val ivdManager = ViewTypeDelegateManager() + + @Suppress("PropertyName", "VariableNaming", "ktlint:standard:backing-property-naming") + protected val _list = mutableListOf() + + lateinit var adapter: AP + + override val list: List 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) { + 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) { + 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 : IRvAdapter { @@ -73,6 +164,57 @@ internal interface IMutableRvAdapter : IRvAdapter { fun removeItem(index: Int) fun removeItem(item: M) + + class Impl : + IRvAdapter.Impl(), + IMutableRvAdapter + where AP : IRvAdapter, + AP : IRvBinding, + AP : RecyclerView.Adapter { + + override var list: List + get() = super.list + set(value) { + _list.clear() + _list.addAll(flat(value)) + } + + override fun refreshItems(items: List) { + 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): List { + val result = mutableListOf() + original.forEach { findLeaf(it, result) } + return result + } + + private fun findLeaf(model: M, list: MutableList) { + 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") diff --git a/adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/core/IRvBinding.kt b/adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/core/IRvBinding.kt new file mode 100644 index 000000000..1bb718a5a --- /dev/null +++ b/adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/core/IRvBinding.kt @@ -0,0 +1,21 @@ +package io.goooler.demoapp.adapter.rv.core + +import androidx.databinding.ViewDataBinding + +interface IRvBinding { + + /** + * 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) +} diff --git a/adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/core/RvAdapterHelper.kt b/adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/core/RvAdapterHelper.kt deleted file mode 100644 index e98759c34..000000000 --- a/adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/core/RvAdapterHelper.kt +++ /dev/null @@ -1,142 +0,0 @@ -package io.goooler.demoapp.adapter.rv.core - -import android.view.ViewGroup -import androidx.annotation.LayoutRes -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 - -/** - * Created on 2020/10/22. - * - * FeAdapterHelper. It can be easily used in adapter. - * - * @author feling - * @version 1.0.0 - * @since 1.0.0 - */ -@Suppress("TooManyFunctions") -internal class RvAdapterHelper(private val adapter: IRvAdapter) { - - private val ivdManager = ViewTypeDelegateManager() - private val _list = mutableListOf() - - var list: List - get() = _list - set(value) { - _list.clear() - _list.addAll(transform(value)) - } - - /** - * Called when RecyclerView starts observing this Adapter. - */ - fun onAttachedToRecyclerView(recyclerView: RecyclerView) { - adapter.initManager(ivdManager) - fixSpanSize(recyclerView) - } - - /** - * Called when RecyclerView stops observing this Adapter. - */ - fun onDetachedFromRecyclerView(recyclerView: RecyclerView) { - ivdManager.clear() - recyclerView.adapter = null - } - - /** - * Called when RecyclerView needs a new ViewHolder of the given type to represent an item. - */ - fun onCreateViewHolder(parent: ViewGroup, @LayoutRes viewType: Int): BindingViewHolder { - return adapter.createVH(parent, viewType).also { - adapter.onCreateVHForAll(it.binding) - ivdManager.onCreateVH(it.binding, viewType) - } - } - - /** - * Called by RecyclerView to display the data at the specified position. - */ - fun onBindViewHolder( - holder: BindingViewHolder, - position: Int, - payloads: List = emptyList(), - ) { - adapter[position]?.let { - setFullSpan(holder, it) - adapter.onBindVHForAll(holder.binding, it, payloads) - ivdManager.onBindVH(holder.binding, it, payloads) - holder.binding.executePendingBindings() - } - } - - /** - * Compare the list to find the same items and refresh them. - */ - fun refreshItems(items: List, notify: (Int) -> Unit) { - transform(items).forEach { - if (it in _list) { - notify(_list.indexOf(it)) - } - } - } - - /** - * Transform data list. Always return a new list. - */ - fun transform(original: List): List { - val result = mutableListOf() - original.forEach { findLeaf(it, result) } - return result - } - - fun removeItem(index: Int, notify: (Int) -> Unit) { - _list.removeAt(index) - notify(index) - } - - fun removeItem(item: M, notify: (Int) -> Unit) { - _list.indexOf(item).takeIf { it != -1 }?.let { - removeItem(it, notify) - } - } - - /** - * Recursively traversing all leaf nodes. - */ - @Suppress("UNCHECKED_CAST") - private fun findLeaf(model: M, list: MutableList) { - if (model is IVhModelWrapper<*>) { - if (model.viewType != -1) list += model - model.subList.forEach { findLeaf(it as M, list) } - } else { - list += model - } - } - - /** - * 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 { - (adapter[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 - } - } -} diff --git a/adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/core/ViewTypeDelegate.kt b/adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/core/ViewTypeDelegate.kt index 31d352bdf..ccd654d77 100644 --- a/adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/core/ViewTypeDelegate.kt +++ b/adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/core/ViewTypeDelegate.kt @@ -1,8 +1,5 @@ package io.goooler.demoapp.adapter.rv.core -import androidx.annotation.LayoutRes -import androidx.databinding.ViewDataBinding - /** * Created on 2020/10/21. * @@ -12,26 +9,6 @@ import androidx.databinding.ViewDataBinding * @version 1.0.0 * @since 1.0.0 */ -interface ViewTypeDelegate { - - /** - * The delegate takes effect when this.getViewType() == model.getViewType(). - */ - @get:LayoutRes - val viewType: Int - - /** - * What to do when creating the viewHolder. - * - * @param binding ViewDataBinding - */ - fun onCreateVH(binding: DB) - - /** - * What to do when binding the viewHolder. - * - * @param binding ViewDataBinding - * @param model model - */ - fun onBindVH(binding: DB, model: M, payloads: List) -} +interface ViewTypeDelegate : + IVhModelType, + IRvBinding diff --git a/adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/core/ViewTypeDelegateManager.kt b/adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/core/ViewTypeDelegateManager.kt index 0ef4d8274..09e738a02 100644 --- a/adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/core/ViewTypeDelegateManager.kt +++ b/adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/core/ViewTypeDelegateManager.kt @@ -16,7 +16,7 @@ import androidx.databinding.ViewDataBinding */ class ViewTypeDelegateManager { - private val ivDs = SparseArrayCompat>() + private val ivDs = SparseArrayCompat>() /** * When creating viewHolder. if VTD.getViewType() == viewType executes VTD.onCreateVH(). @@ -46,8 +46,8 @@ class ViewTypeDelegateManager { * @param ivd VTD */ @Suppress("UNCHECKED_CAST") - fun add(ivd: ViewTypeDelegate) { - ivDs[ivd.viewType] = ivd as ViewTypeDelegate + fun add(ivd: ViewTypeDelegate) { + ivDs[ivd.viewType] = ivd as ViewTypeDelegate } /** diff --git a/adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/diff/BaseRvDiffAdapter.kt b/adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/diff/BaseRvDiffAdapter.kt index d38057773..72aabef45 100644 --- a/adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/diff/BaseRvDiffAdapter.kt +++ b/adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/diff/BaseRvDiffAdapter.kt @@ -1,15 +1,11 @@ package io.goooler.demoapp.adapter.rv.diff -import android.view.ViewGroup -import androidx.annotation.LayoutRes import androidx.recyclerview.widget.AsyncDifferConfig import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter -import androidx.recyclerview.widget.RecyclerView import io.goooler.demoapp.adapter.rv.core.BindingViewHolder import io.goooler.demoapp.adapter.rv.core.IMutableRvAdapter -import io.goooler.demoapp.adapter.rv.core.RvAdapterHelper -import kotlinx.collections.immutable.toImmutableList +import io.goooler.demoapp.adapter.rv.core.IRvBinding /** * Created on 2020/10/22. @@ -20,71 +16,31 @@ import kotlinx.collections.immutable.toImmutableList * @version 1.0.0 * @since 1.0.0 */ -abstract class BaseRvDiffAdapter : - ListAdapter, - IMutableRvAdapter { - - private val helper by lazy(LazyThreadSafetyMode.NONE) { RvAdapterHelper(this) } - - constructor(callback: DiffCallBack = DiffCallBack()) : super(callback) - - constructor(config: AsyncDifferConfig) : super(config) - - 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, +@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE") +abstract class BaseRvDiffAdapter private constructor( + asyncDifferConfig: AsyncDifferConfig, + private val delegate: IMutableRvAdapter.Impl>, +) : ListAdapter(asyncDifferConfig), + IRvBinding, + IMutableRvAdapter by delegate { + + constructor(callback: DiffCallback = DiffCallback()) : this( + AsyncDifferConfig.Builder(callback).build(), + IMutableRvAdapter.Impl(), ) { - helper.onBindViewHolder(holder, position) + @Suppress("LeakingThis") + delegate.adapter = this } - override fun onBindViewHolder( - holder: BindingViewHolder, - position: Int, - payloads: List, - ) { - helper.onBindViewHolder(holder, position, payloads) + constructor(config: AsyncDifferConfig) : this(config, IMutableRvAdapter.Impl()) { + @Suppress("LeakingThis") + delegate.adapter = this } - @LayoutRes - override fun getItemViewType(position: Int): Int = - getItem(position).viewType - - override operator fun get(position: Int): M = getItem(position) - override var list: List - get() = helper.list.toImmutableList() + get() = delegate.list set(value) { - helper.list = value - submitList(helper.transform(value)) + delegate.list = value + submitList(delegate.list) } - - /** - * Please do not use it with setList() ! - */ - override fun refreshItems(items: List) { - helper.refreshItems(items, ::notifyItemChanged) - } - - override fun removeItem(index: Int) { - helper.removeItem(index, ::notifyItemRemoved) - } - - override fun removeItem(item: M) { - helper.removeItem(item, ::notifyItemRemoved) - } } diff --git a/adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/diff/DiffCallBack.kt b/adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/diff/DiffCallback.kt similarity index 88% rename from adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/diff/DiffCallBack.kt rename to adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/diff/DiffCallback.kt index fecedf222..49b53349a 100644 --- a/adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/diff/DiffCallBack.kt +++ b/adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/diff/DiffCallback.kt @@ -2,7 +2,7 @@ package io.goooler.demoapp.adapter.rv.diff import androidx.recyclerview.widget.DiffUtil -open class DiffCallBack : DiffUtil.ItemCallback() { +open class DiffCallback : DiffUtil.ItemCallback() { /** * Call this first. diff --git a/adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/paging/BaseRvPagingAdapter.kt b/adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/paging/BaseRvPagingAdapter.kt index 5756a8416..dcce534ff 100644 --- a/adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/paging/BaseRvPagingAdapter.kt +++ b/adapter/src/main/kotlin/io/goooler/demoapp/adapter/rv/paging/BaseRvPagingAdapter.kt @@ -1,14 +1,14 @@ package io.goooler.demoapp.adapter.rv.paging -import android.view.ViewGroup import androidx.annotation.LayoutRes +import androidx.paging.CombinedLoadStates import androidx.paging.LoadState import androidx.paging.PagingDataAdapter import androidx.recyclerview.widget.RecyclerView import io.goooler.demoapp.adapter.rv.core.BindingViewHolder import io.goooler.demoapp.adapter.rv.core.IRvAdapter -import io.goooler.demoapp.adapter.rv.core.RvAdapterHelper -import io.goooler.demoapp.adapter.rv.diff.DiffCallBack +import io.goooler.demoapp.adapter.rv.core.IRvBinding +import io.goooler.demoapp.adapter.rv.diff.DiffCallback import io.goooler.demoapp.adapter.rv.diff.IDiffVhModelType /** @@ -18,71 +18,56 @@ import io.goooler.demoapp.adapter.rv.diff.IDiffVhModelType * @version 1.0.0 * @since 1.0.0 */ -abstract class BaseRvPagingAdapter(callback: DiffCallBack = DiffCallBack()) : - PagingDataAdapter(callback), - IRvAdapter { - - private val helper by lazy(LazyThreadSafetyMode.NONE) { RvAdapterHelper(this) } +@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE") +abstract class BaseRvPagingAdapter private constructor( + callback: DiffCallback, + private val delegate: IRvAdapter.Impl>, +) : PagingDataAdapter(callback), + IRvBinding, + IRvAdapter by delegate { var onLoadStatusListener: OnLoadStatusListener? = null override val list: List get() = snapshot().items + constructor(callback: DiffCallback = DiffCallback()) : this(callback, IRvAdapter.Impl()) { + @Suppress("LeakingThis") + delegate.adapter = this + } + override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { super.onAttachedToRecyclerView(recyclerView) - helper.onAttachedToRecyclerView(recyclerView) - observeLoadState() + delegate.onAttachedToRecyclerView(recyclerView) + addLoadStateListener(loadStateListener) } 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, - ) { - helper.onBindViewHolder(holder, position, payloads) + delegate.onDetachedFromRecyclerView(recyclerView) + removeLoadStateListener(loadStateListener) } @LayoutRes - override fun getItemViewType(position: Int): Int = - getItem(position)?.viewType ?: 0 + override fun getItemViewType(position: Int): Int = getItem(position)?.viewType ?: 0 override operator fun get(position: Int): M? = getItem(position) - private fun observeLoadState() { - addLoadStateListener { - when { - it.refresh is LoadState.Loading -> onLoadStatusListener?.onRefresh() - it.append is LoadState.Loading -> onLoadStatusListener?.onLoadMore() - else -> { - onLoadStatusListener?.onNotLoading() - if (it.refresh is LoadState.Error) { - when (val throwable = (it.refresh as LoadState.Error).error) { - is PagingSourceException.EmptyDataException -> onLoadStatusListener?.onEmpty() - else -> onLoadStatusListener?.onError(throwable) - } + private val loadStateListener: (CombinedLoadStates) -> Unit = { + when { + it.refresh is LoadState.Loading -> onLoadStatusListener?.onRefresh() + it.append is LoadState.Loading -> onLoadStatusListener?.onLoadMore() + else -> { + onLoadStatusListener?.onNotLoading() + if (it.refresh is LoadState.Error) { + when (val throwable = (it.refresh as LoadState.Error).error) { + is PagingSourceException.EmptyDataException -> onLoadStatusListener?.onEmpty() + else -> onLoadStatusListener?.onError(throwable) } - if (it.append is LoadState.Error) { - when (val throwable = (it.append as LoadState.Error).error) { - is PagingSourceException.NoMoreDataException -> onLoadStatusListener?.onNoMoreData() - else -> onLoadStatusListener?.onError(throwable) - } + } + if (it.append is LoadState.Error) { + when (val throwable = (it.append as LoadState.Error).error) { + is PagingSourceException.NoMoreDataException -> onLoadStatusListener?.onNoMoreData() + else -> onLoadStatusListener?.onError(throwable) } } } diff --git a/biz/main/src/main/kotlin/io/goooler/demoapp/main/ui/adapter/MainPagingRvAdapter.kt b/biz/main/src/main/kotlin/io/goooler/demoapp/main/ui/adapter/MainPagingRvAdapter.kt index eb547712e..b5a5e123e 100644 --- a/biz/main/src/main/kotlin/io/goooler/demoapp/main/ui/adapter/MainPagingRvAdapter.kt +++ b/biz/main/src/main/kotlin/io/goooler/demoapp/main/ui/adapter/MainPagingRvAdapter.kt @@ -10,11 +10,11 @@ class MainPagingRvAdapter( private val listener: OnEventListener, ) : BaseRvPagingAdapter() { - override fun onCreateVHForAll(binding: ViewDataBinding) { + override fun onCreateVH(binding: ViewDataBinding) { binding.bindListener(listener) } - override fun onBindVHForAll( + override fun onBindVH( binding: ViewDataBinding, model: MainCommonVhModel, payloads: List, diff --git a/biz/main/src/main/kotlin/io/goooler/demoapp/main/ui/adapter/MainSrlRvAdapter.kt b/biz/main/src/main/kotlin/io/goooler/demoapp/main/ui/adapter/MainSrlRvAdapter.kt index 09ff6d9de..ef7aec659 100644 --- a/biz/main/src/main/kotlin/io/goooler/demoapp/main/ui/adapter/MainSrlRvAdapter.kt +++ b/biz/main/src/main/kotlin/io/goooler/demoapp/main/ui/adapter/MainSrlRvAdapter.kt @@ -2,7 +2,7 @@ package io.goooler.demoapp.main.ui.adapter import androidx.databinding.ViewDataBinding import io.goooler.demoapp.adapter.rv.diff.BaseRvDiffAdapter -import io.goooler.demoapp.adapter.rv.diff.DiffCallBack +import io.goooler.demoapp.adapter.rv.diff.DiffCallback import io.goooler.demoapp.common.util.ImageLoader import io.goooler.demoapp.common.util.asConfig import io.goooler.demoapp.main.databinding.MainCommonRvItemBinding @@ -12,13 +12,13 @@ import io.goooler.demoapp.main.util.bindModel class MainSrlRvAdapter( private val listener: OnEventListener, -) : BaseRvDiffAdapter(DiffCallBack().asConfig()) { +) : BaseRvDiffAdapter(DiffCallback().asConfig()) { - override fun onCreateVHForAll(binding: ViewDataBinding) { + override fun onCreateVH(binding: ViewDataBinding) { binding.bindListener(listener) } - override fun onBindVHForAll( + override fun onBindVH( binding: ViewDataBinding, model: MainCommonVhModel, payloads: List,