Simplifying the work with RecyclerView

Vitaly Vivchar
AndroidPub
Published in
5 min readApr 10, 2018

--

There are already a lot of articles on this topic, all of them mainly offer solutions for convenient reusing of cells in RecyclerView. Today we will go a little further and approach the simplicity comparable with DataBinding.

If you don’t already use DataBinding for lists and do it in the old manner — then this article is for you.

Introduction

To quickly dive into the context of the article, let’s analyze what we now have when using the universal adapter from the previous articles:

  1. A RecyclerView with multiple item types;
  2. Easy handling of lists — RendererRecyclerViewAdapter.

To implement the simplest list using RendererRecyclerViewAdapter v2.x.x you need to:

1. Add ViewModel interface for each cell model:

public class YourModel implements ViewModel {    //...    public String getYourText() { … }}

2. Make a classic ViewHolder implementation:

<?xml version="1.0" encoding="utf-8"?>
<TextView
android:id = "@+id/yourTextView"
xmlns:android = "http://schemas.android.com/apk/res/android"
android:layout_width = "match_parent"
android:layout_height = "50dp"
/>
public class YourViewHolder extends RecyclerView.ViewHolder { public
TextView yourTextView;
public
RectViewHolder(View v) {
super(itemView);
yourTextView = (TextView) v.findViewById(R.id.yourTextView);
}
//...
}

3. Implement ViewRenderer:

public class YourViewRenderer 
extends ViewRenderer<YourModel, YourViewHolder> {
public
YourViewRenderer(Class<YourModel> type, Context context) {
super(type, context);
}
public
void bindView(YourModel model, YourViewHolder holder) {
holder.yourTextView.setText(model.getYourText());
//...
}
public
YourViewHolder createViewHolder(ViewGroup parent) {
return new YourViewHolder(inflate(
R.layout.your_layout,
parent
));
}
}

4. Initialize Adapter and give the necessary data to it:

RendererRecyclerViewAdapter a = new RendererRecyclerViewAdapter();
a.registerRenderer(new YourViewRenderer(
YourModel.class,
getContext()
));
a.setItems(getYourModelList());

Knowing about DataBinding and its simple implementation, there is a question — why so much extra code, the main thing is binding — a comparison of these models with layout. From binding you can not go anywhere.

In the classical implementation, we use the bindView() method, everything else is just a preparation for it (implementation and initialization ViewHolder).

What is ViewHolder and why is it needed?

In Fragment, Activity and RecyclerView, we often use this pattern, so what do we need it for? What are the pros and cons of it?

Advantages:

  • No need to use findViewById() each time and to specify an ID;
  • No need to always spend CPU time searching for a specific ID in xml;
  • Convenient access to the element through the created field.

Disadvantages:

  • You have to write an additional class;
  • You have to create a field with a similar name for each ID in xml;
  • When you change an ID, you need to rename the field in ViewHolder.

With some disadvantages, third-party libraries are fine, for example ButterKnife, but in the RecyclerView case it will not help us much — we will not get rid of ViewHolder. In DataBinding we can create a universal ViewHolder, because the Binding responsibility is in XML. What can we do?

Creating Default ViewHolder

If we use the standard implementation of RecyclerView.ViewHolder as a stub in createViewHolder() method, we will have to use findViewById method every time in bindView(), let’s sacrifice the Advantages and see what happens.

Since this is an abstract class, let’s add an empty implementation of the new default ViewHolder:

public class ViewHolder extends RecyclerView.ViewHolder {     public
ViewHolder(View itemView) {
super(itemView);
}
}

We replace ViewHolder in our ViewRenderer:

public class YourViewRenderer
extends ViewRenderer<YourModel, ViewHolder> {
public
YourViewRenderer(Class<YourModel> type, Context ctx) {
super(type, ctx);
}
public
void bindView(YourModel m, ViewHolder h) {
TextView textView =((TextView)h.itemView.findViewById(R.id.yourTextView));
textView.setText(m.getYourText());
}
public
ViewHolder createViewHolder(ViewGroup parent) {
return new ViewHolder(inflate(
R.layout.your_layout,
parent
));
}
}

The resulting advantages:

  • No need to implement ViewHolder for each cell;
  • The implementation of createViewHolder method can be moved to the base class.

Now we will analyze the lost pluses. Since we are viewing ViewHolder within RecyclerView, we will only refer to it in bindView() method. Therefore, the first and third items are not very useful to us:

  • No need to use findViewById() each time and to specify an ID;
  • Convenient access to the element through the created field.

But we can not sacrifice performance, so let’s decide something. The implementation of ViewHolder allows us to “cache” the found view. So let’s add it to the default ViewHolder:

public class ViewHolder extends RecyclerView.ViewHolder {    private final SparseArray<View> mCachedViews = new SparseArray<>();    public ViewHolder(View itemView) {
super(itemView);
}
public <T extends View> T find(int ID) {
return (T) findViewById(ID);
}
private View findViewById(int ID) {
final View cachedView = mCachedViews.get(ID);
if (cachedView != null) {
return cachedView;
}
final View view = itemView.findViewById(ID);
mCachedViews.put(ID, view);
return view;
}
}

Thus, after the first call of bindView(), the ViewHolder will know about all its views and subsequent calls will use cached values.

Now we’ll remove all unnecessary ones from the base ViewRender, add a new parameter to the constructor to transfer the ID of the file and see what happens:

public class YourViewRenderer
extends ViewRenderer<YourModel, ViewHolder> {
public YourViewRenderer(int layoutID,
Class<YourModel> type,
Context context) {
super(layoutID, type, context);
}
public void bindView(YourModel model, ViewHolder holder) {
((TextView)holder.find(R.id.yourTextView))
.setText(model.getYourText());
}
}

In terms of the amount of code, it looks much better: one constructor remains and it is always the same. And do we really need to create a new ViewRenderer each time for the sake of one method? I think not. We solve this problem through delegation and an additional parameter in the constructor, let’s have a look:

public class ViewBinder<M extends ViewModel>
extends ViewRenderer<M, ViewHolder> {
private final Binder mBinder; public ViewBinder(int layoutID,
Class<M> type,
Context context,
Binder<M> binder) {
super(layoutID, type, context);
mBinder = binder;
}
public void bindView(M model, ViewHolder holder) {
mBinder.bindView(model, holder);
}
public interface Binder <M> {
void bindView(M model, ViewHolder holder);
}
}

Adding a cell is shortened to:

...adapter.registerRenderer(new ViewBinder<>(
R.layout.your_layout,
YourModel.class,
getContext(),
(model, holder) -> {
((TextView)holder.find(R.id.yourTextView))
.setText(model.getYourText());
}
));
...

Let’s list the advantages of this solution:

  • No need to create ViewHolder each time and create variables for views;
  • No need to create ViewRenderer each time and write extra code;
  • No need to rename anything when changing the view ID;
  • All the data about the view (layoutID, concreteViewID, cast) are in one place.

To avoid a lot of casts and simplify binding I added main methods to ViewHolder:

public ViewHolder setText(int ID, CharSequence text) {   
((TextView) find(ID)).setText(text);
return this;
}
public ViewHolder setTextSize(int ID, float size) {
((TextView) find(ID)).setTextSize(size);
return this;
}
public ViewHolder setImageDrawable(int ID, Drawable drawable) {
((ImageView) find(ID)).setImageDrawable(drawable);
return this;
}
public ViewHolder setAlpha(int ID, int alpha) {
find(ID).setAlpha(alpha);
return this;
}
//other main methods

Conclusion

By sacrificing small advantages we’ve got a fairly simple solution for adding new cells, this will undoubtedly simplify the work with RecyclerView.

The article provides only a simple example for understanding, the current implementation allows:

  • Working with Nested RecyclerView;
  • Saving and restoring the state of the cell in the scroll;
  • Working with Payload when using DiffUtil;
  • Adding Load More Indicator when loading data.

Github Library

--

--