CellAdapter: a Simpler Way to Use RecyclerView

In terms of Android development, Adapter is a design pattern that separates business logic from view creation, handles mapping a bunch of data and dispatching updates to views.

Introduction

Displaying lists of data is a frequent functionality for most applications. Android SDK provides a developer with RecyclerView for showing a list of data. Often you can find a lot of different subclasses of RecyclerView.Adapter in one project: one adapter for each list presentation. This brings a lot of code duplication and increases probability of errors introduced by a developer due to copy-paste. Routinely, only the view layout and the data-to-view mapping differ from each other.

What is CellAdapter?

For the purpose of having our project code DRY, we’ve created CellAdapter. CellAdapter is a library used to simplify working with RecyclerView via a simple interface. It provides an opportunity to easily support multiple view types, to separate ViewHolders from Adapter class, map data to a view in a convenient way, to register and handle UI callbacks for each ViewHolder.

CellAdapter has 3 core classes components:

  • CellAdapter – the main adapter class, a subclass of RecyclerView.Adapter.
  • Cell – the main abstract view class for each type of data, a subclass of ViewHolder. Used to map data to a view.
  • Cell.Listener – nested Cell interface. It needs to be implemented to send callbacks from Cell.

Setup

Add the JitPack repository in your root build.gradle at the end of repositories:

allprojects {
repositories {
maven { url “https: //jitpack.io” }
}
}

Add the dependency:

dependencies {
compile ‘com.github.erva.CellAdapter:celladapter:2.0.4’

compile ‘com.github.erva.CellAdapter:celladapter-kotlin:2.0.4’

}

Add the following proguard rules:

#CellAdapter
-keepclasseswithmembers public class * extends io.erva.celladapter.** { *; }

How it works?

The example shows how easy it is to use CellAdapter with different data types. For detailed information, please review kotlin sample and/or java sample project on GitHub.

Model

First of all, let’s create a simple data class. There are no needs to extend some base objects: Cell class could work with any objects.

AlphaModel.kt

data class AlphaModel(val alpha: String)

Cell

The next step is to implement Cell for AlphaModel: AlphaCell needs to be extended from Cell class. CellAdapter library provides a special annotation (@Layout), which is used for declaring the view layout for each Cell.

Let’s review Cell core methods:

  • bindView() – abstract method. Called each time when a view is recycled. In this method, a model data has to be set up to a view.
  • item() – returns an instance of a data object.
  • listener() – returns an instance of Cell callback. The callback can be Nullable if Cell.Listener wasn’t set.

AlphaCell.kt

@Layout (R.layout.item_base_alpha)

class AlphaCell (view: View) : Cell<AlphaModel, AlphaCell.Listener>(view) {

override fun bindView() {

with(view) {

tv_alpha.text = item().alpha

btn_one_press.setOnClickListener { listener()?.onPressOne(item()) }

btn_two_press.setOnClickListener { listener()?.onPressTwo(item()) }

}

}

 

interface Listener : Cell.Listener<AlphaModel> {

fun onPressOne (item: AlphaModel)

fun onPressTwo(item: AlphaModel)

}

}

layout/item_base_alpha.xml

<?xml version=“1.0” encoding=“utf-8”?>
<LinearLayout xmlns:android=“http: //schemas.android.com/apk/res/android” …>
<TextView android:id=“@+id/tv_alpha” …/>
<Button android:id=“@+id/btn_one_press” …/>
<Button android:id=“@+id/btn_two_press” …/>
</LinearLayout>

Adapter

The main work is done. The only thing left is to create an adapter, register all cells, and fill the adapter with data.

The operations of creating an adapter and registering cells are pretty easy. CellAdapter provides a convenient approach for these operations. All you need is to specify classes of Cell and Model and provide a listener implementation if required.

private val adapter: CellAdapter = CellAdapter().let {
it.cell(AlphaCell::class) {
item(AlphaModel::class)
listener(object : AlphaCell.Listener {

override fun onPressOne(item: AlphaModel) {
showToast(String.format(“%s%n press button %d”, item.alpha, 1))
}

override fun onPressTwo(item: AlphaModel) {
showToast(String.format(“%s%n press button %d”, item.alpha, 2))
}

override fun onCellClicked(item: AlphaModel) {
showToast(item.alpha)
}
})
}

it.cell(BetaCell::class) {
item(BetaModel::class)
listener(object : BetaCell.Listener {
override fun onCellClicked(item: BetaModel) {
showToast(item.beta)
}
})
}

it.cell(GammaCell::class) {
item(GammaModel::class)
}
}

 

Now let’s set up RecyclerView and fill the adapter with data.

recycler_view.layoutManager = LinearLayoutManager(this)
recycler_view.adapter = adapter
for (i in 0..33) {
adapter.items.add(AlphaModel(String.format(“AlphaModel %d”, i)))
adapter.items.add(BetaModel(String.format(“BetaModel %d”, i)))
adapter.items.add(GammaModel(String.format(“GammaModel %d”, i)))
}

adapter.notifyDataSetChanged()

Selections

CellAdapter also has a built-in adapter implementation for single/multi selection lists – SelectableCellAdapter. All you need is to pass the desired SelectionManager to the SelectableAdapter constructor. Two managers have already been implemented: SingleSelectionManager and MultiSelectionManager. Feel free to create your own manager – extend SelectionManager class and implement your selection logic inside.

SelectionManager contains methods:

fun toggleSelection(position: Int)

fun setSelection(position: Int, isSelected: Boolean)

fun isSelected(position: Int): Boolean

fun clearSelections(notify: Boolean)

fun getSelectedItemCount(): Int

fun getSelectedPositions(): Collection<Int>

fun isSelected(position: Int): Boolean

SingleSelectionManager has additional methods:

fun getSelectedPosition(): Int

MultiSelectionManager has methods for bulk manipulations:

fun isAllSelected(): Boolean

fun setSelectedPositions(selectionPositions: List<Int>)

fun setSelectionForAll(isSelected: Boolean)

For selection, cell has to extend SelectableCell. Take a look at the example:

@Layout(R.layout.item_single)

class SingleChoiceCell(view: View) : SelectableCell<SingleChoiceModel, Cell.Listener<SingleChoiceModel>>(view) {

 

override fun bindView() {

view.rb_single.isChecked = selectionManager.isSelected(adapterPosition)

view.rb_single.setOnClickListener {

selectionManager.toggleSelection(adapterPosition)

listener()?.onCellClicked(item())

}

}

}

 

view.rb_single is <RadioButton android:id=“@+id/rb_single”…/>

SelectableCell has access to selectionManager where it can get data for an initial view state and modify the state from a cell.

Selectable adapter has additional constructor params:

adapter: CellAdapter = SelectableCellAdapter(selectedPositions: MutableSet<Int> = HashSet<Int>(), val selectionManager: SelectionManager)

Using cells with ButterKnife (Java only)

Cells can be used with ButterKnife. However, to CellAdapter lib, ButterKnife was not added in order to minimize additional dependency count.

Feel free to create BaseCell and extend new cells from it.

public abstract class BaseCell<ITEM, LISTENER extends Cell.Listener<ITEM>> extends Cell<ITEM, LISTENER> {

public BaseCell(View view) {

super(view);

ButterKnife.bind(this, view);

}

}

and then:

@Layout(R.layout.item_base_alpha)

public class AlphaCell extends BaseCell<AlphaModel, AlphaCell.Listener> {

 

@BindView(R.id.tv_alpha) TextView textView;

 

@OnClick(R.id.btn_one_press)

public void onPressOne() {

if (getListener() != null) getListener().onPressOne(getItem());

}

}

Give it a shot!

The concept of CellAdapter is simple, but the result allowed focusing on what’s important  –  manipulating and managing data.

Check CellAdapter out on Github.


I would like to thank Ervin Martirosyan, for his important contribution to this article.

Share article: