Skip to content

netcosports/CompositeAdapter_Android

Repository files navigation

Maven Central License

CompositeAdapter

The CompositeAdapter extends ListAdapter and delegates all UI logic to a Cell<DATA, VIEW_HOLDER> created by the ViewModel. Cells handles all work with ViewHolders, DiffUtils and ItemDecorations.

Dependencies

repositories {
    mavenCentral()
}

implementation("io.github.netcosports.compositeadapter:composite-adapter:${compositeAdapterVersion}")

Samples

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

Usage

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)
}

2. Setup RecyclerView:

// 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())

3. Submit Data:

val items: List<GenericCell> = listOf(SampleCell(...), HorizontallStoriesCell(...), TitleCell(...), NewsCell(...), ...)
adapter.submitList(items)

That's all.

ItemDecorations

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.

Advanced Usage

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.

1. Create a base ViewHolder:

Forget about ViewHolders 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 Cells.

2. Create a base Cell:

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.

3. Create a real Cell using basic implementations

ViewBinding:

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
    }
}

DataBinding:

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.