A RecyclerView adapter reducing boilerplate and improving item reusability
Never have to write an adapter again. Just use the provided ItemAdapter
across the whole project
and only define ItemRenderer
for your items. You can register multiple renderers to one adapter,
which allows you to display multiple item types in a single recycler view. It also helps you to
reuse the same items across different recycler views in your app.
Add the JitPack repository to your build file:
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
Add the dependency
dependencies {
implementation 'com.github.MattSkala:recyclerview-itemadapter:0.5'
}
The item model defines the item that is going to be rendered. In a simple case it can be only a wrapper for your data model, but it can also contain hints and formatting functions useful for rendering.
class ExampleItem(val title: String, val subtitle: String) : Item()
The item renderer defines how the item should be rendered. The base ItemRenderer
requires you to
implement two methods. Firstly, createViewHolder
should create a new view holder instance
containing the item view. Secondly, bindView
should bind a given item to the previously
created (and possibly recycled) view holder.
class ExampleRenderer : ItemRenderer<ExampleItem, ItemViewHolder>(ExampleItem::class.java) {
override fun createViewHolder(parent: ViewGroup): ItemViewHolder {
val inflater = LayoutInflater.from(parent.context)
return ExampleViewHolder(inflater.inflate(R.layout.item_example, parent, false))
}
override fun bindView(item: ExampleItem, holder: ItemViewHolder) = with(holder) {
title.text = item.title
subtitle.text = item.subtitle
}
}
If you do not need direct access to a view holder, you can subclass ItemViewRenderer
instead,
which allows to return a view from the createView
method and wraps it in a view holder internally.
class ExampleRenderer : ItemViewRenderer<ExampleItem, MyView>(ExampleItem::class.java) {
override fun createView(parent: ViewGroup): MyView {
return MyView(parent.context)
}
override fun bindView(item: ExampleItem, view: MyView) = with(view) {
view.title = item.title
view.subtitle = item.subtitle
}
}
With ItemLayoutRenderer
, you only have to return the layout resource ID in getLayoutResourceId
,
and the renderer inflates the layout automatically. Note that this class is easy to misuse. If
you use synthetic properties to access child views in bindView
, the views will not be cached and
findViewById
will be called on every invocation of bindView
method!
class ExampleRenderer : ItemLayoutRenderer<ExampleItem, ExampleView>(ExampleItem::class.java) {
override fun getLayoutResourceId(): Int {
return R.layout.item_example
}
override fun bindView(item: ExampleItem, view: ExampleView) = with(view) {
title.text = item.title
subtitle.text = item.subtitle
}
}
For this reason, it is recommended to use BindingItemRenderer
which provides support for View
Binding, a type-safe way to access layout views with caching and the minimum amount of boilerplate:
class ExampleRenderer : BindingItemRenderer<ExampleItem, ItemExampleBinding>(
ExampleItem::class.java,
ItemExampleBinding::inflate
) {
override fun bindView(item: ExampleItem, binding: ItemExampleBinding) {
binding.title.text = item.title
binding.subtitle.text = item.subtitle
}
}
Finally, instantiate ItemAdapter
and register your renderers. You can register multiple renderers
for different item types. Note that you should not register multiple renderers for the same item
type, which would result in IllegalArgumentException
.
val adapter = ItemAdapter()
adapter.items = listOf(ExampleItem("Hello", "World"))
val exampleRenderer = ExampleRenderer()
adapter.registerRenderer(exampleRenderer)
The ItemAdapter
has a built-in support for DiffUtil
, which allows it to only re-render items
that have been changed when data has been changed. To take advantage of it, pass an updated item
list to ItemAdapter.updateItems
method whenever the data changes.
adapter.updateItems(items)
Additionally, you can override Item.areItemsTheSame
and Item.areContentsTheSame
methods to
provide custom logic for determining if items represent the same entity or that the content has
changed. Refer to the documentation of the DiffUtil.Callback
class for the expected behavior.