The CompositeAdapter
extends ListAdapter
and delegates all UI logic to a Cell<DATA, VIEW_HOLDER>
created by the ViewModel
.
Cell
s handles all work with ViewHolder
s, DiffUtil
s and ItemDecoration
s.
repositories {
mavenCentral()
}
implementation("io.github.netcosports.compositeadapter:composite-adapter:${compositeAdapterVersion}")
Sample | Description |
---|---|
Basic | A simple setup how to create and show Cell inside CompositeAdapter |
Decorations | Using a different ItemDecoration for each Cell |
Different bindings | Using ViewBinding /DataBinding /CustomViews |
Inner RecyclerView/Webview/VideoPlayer or other complex view |
Handle binding for complex views via payload and save the scroll state of inner RecyclerViews |
State as Cells | Show all requests (including Loading/Error/Empty/Data states and SwipeRefresh/Reload/Error actions) in a single RecyclerView without any additional Views |
1. Implement SampleCell:
data class SampleCell( override val data: SampleUI, // Must be kotlin data class or with correct equals override val decoration: ItemDecoration? = null, // ItemDecoration for this SampleCell instance only override val onClickListener: ((GenericClickItem<SampleUI>) -> Unit)? = null // Root View.OnClickListener ) : Cell<SampleUI, SampleCell.SampleViewHolder> { override val uniqueId: String = data.id // Must be unique for this viewType override val viewType: Int = R.layout.sample_cell // Can be generated via ids.xml override fun onCreateViewHolder( inflater: LayoutInflater, parent: ViewGroup, viewType: Int ): SampleViewHolder { return SampleViewHolder(SampleCellBinding.inflate(inflater, parent, false)) } override fun onBindViewHolder(holder: SampleViewHolder, position: Int) { holder.binding.text.text = data.name } class SampleViewHolder( val binding: SampleCellBinding ) : RecyclerView.ViewHolder(binding.root) }
// You don't need to list the supported ViewTypes and ViewHolders for the CompositeAdapter. val adapter = CompositeAdapter() recyclerView.adapter = adapter recyclerView.layoutManager = TODO("layoutManager") // Each Cell can have their own ItemDecoration. // To activate this functionality, don't forget to register CompositeItemDecoration. recyclerView.addItemDecoration(CompositeItemDecoration())
val items: List<GenericCell> = listOf(SampleCell(...), HorizontallStoriesCell(...), TitleCell(...), NewsCell(...), ...) adapter.submitList(items)
That's all.
Forget about androidx.recyclerview.widget.ItemDecoration
and use com.originsdigital.compositeadapter.decoration.ItemDecoration
with the additional parameter Cell
instead.
Each Cell
can have their own ItemDecoration
that only affects them.
In most cases we don't need a separate ViewHolder
for each Cell
, so we can remove this copy-paste, for example with the base implementation of Cell
.
Forget about ViewHolder
s for each viewType
, you don't need it anymore. Instead, implement a simple and consistent ViewHolder
, for example:
class ViewBindingViewHolder<VIEW_BINDING : ViewBinding>( val binding: VIEW_BINDING ) : ViewHolder(binding.root)
class DataBindingViewHolder<DATA_BINDING : ViewDataBinding>( val binding: DATA_BINDING ) : ViewHolder(binding.root) { companion object { fun <DATA_BINDING : ViewDataBinding> create( inflater: LayoutInflater, layoutResId: Int, parent: ViewGroup ): DataBindingViewHolder<DATA_BINDING> { return DataBindingViewHolder( DataBindingUtil.inflate( inflater, layoutResId, parent, false ) as DATA_BINDING ) } } }
and use this ViewHolder
in all Cell
s.
If you are using ViewBinding
or DataBinding
, its good idea to have a base Cell
with default implementation of onCreateViewHolder
and onBindViewHolder
(the last one is for DataBinding
only).
abstract class ViewBindingCell<DATA, VIEW_BINDING : ViewBinding> : Cell<DATA, ViewBindingViewHolder<VIEW_BINDING>> { abstract fun createViewBinding( inflater: LayoutInflater, parent: ViewGroup, viewType: Int ): VIEW_BINDING final override fun onCreateViewHolder( inflater: LayoutInflater, parent: ViewGroup, viewType: Int ): ViewBindingViewHolder<VIEW_BINDING> { return ViewBindingViewHolder(createViewBinding(inflater, parent, viewType)) } }
abstract class DataBindingCell<DATA, DATA_BINDING : ViewDataBinding> : Cell<DATA, DataBindingViewHolder<DATA_BINDING>> { @get:LayoutRes abstract val layoutId: Int override val viewType: Int get() = layoutId final override fun onCreateViewHolder( inflater: LayoutInflater, parent: ViewGroup, viewType: Int ): DataBindingViewHolder<DATA_BINDING> { return DataBindingViewHolder.create(inflater, layoutId, parent) } override fun onBindViewHolder(holder: DataBindingViewHolder<DATA_BINDING>, position: Int) { (holder.binding).apply { setVariable(BR.item, data) executePendingBindings() } } }
Now you don't need to copy and paste this code into every Cell
.
data class SampleCell( override val data: SampleEntity, // Must be kotlin data class or with correct equals override val decoration: ItemDecoration? = null, // ItemDecoration for this SampleCell instance only override val onClickListener: ((GenericClickItem<SampleEntity>) -> Unit)? = null // Root View.OnClickListener ) : ViewBindingCell<SampleEntity, SampleCellBinding>() { override val uniqueId: String = data.id // Must be unique for this viewType override val viewType: Int = R.layout.sample_cell // Can be generated via ids.xml override fun createViewBinding( inflater: LayoutInflater, parent: ViewGroup, viewType: Int ): SampleCellBinding { return SampleCellBinding.inflate(inflater, parent, false) } override fun onBindViewHolder( holder: ViewBindingViewHolder<SampleCellBinding>, position: Int ) { holder.binding.text.text = data.text } }
data class SampleCell( override val data: SampleEntity, // Must be kotlin data class or with correct equals override val decoration: ItemDecoration? = null, // ItemDecoration for this SampleCell instance only override val onClickListener: ((GenericClickItem<SampleEntity>) -> Unit)? = null // Root View.OnClickListener ) : DataBindingCell<SampleEntity, SampleCellBinding>() { override val uniqueId: String = data.id // Must be unique for this viewType (by default viewType == layoutId) override val layoutId: Int = R.layout.sample_cell // Can be generated via ids.xml (by default viewType == layoutId) // If you have all bindings inside the R.layout.sample_cell, // you don't need to override onBindViewHolder because everything is done inside the DataBindingCell // But you can move all bindings from layout to onBindViewHolder // override fun onBindViewHolder( // holder: DataBindingViewHolder<SampleCellBinding>, // position: Int // ) { // super.onBindViewHolder(holder, position) // } }
Cell
has all methods related to ViewHolder
, DiffUtil.ItemCallback
and ItemDecoration
, the full list is here.