📜 ⬆️ ⬇️

Easy work with lists - RendererRecyclerViewAdapter

Recently, I often had to rewrite many adapters for lists, and each time I took up my head - the adapter contained business logic, network requests and application routing, and much more. All this is very difficult to change.

At first, as usual, I endured all the excess from adapters to presenters, snippets, and other classes. In the end, I came to the conclusion, why not:

  1. “Secure” your adapters from adding extra logic there;
  2. reuse cell binders;
  3. achieve some kind of versatility to work with several types of cells.

If such problems are familiar to you, then welcome under cat.

From ready-made solutions, I found AdapterDelegates , but it did not fit me according to the first condition.
')

Requirements


To begin with, I wrote out a few already formed requirements:


Implementation


First of all, I looked at what I always do in the adapter, for this I created a test implementation and analyzed the methods I used:

public class Test extends RecyclerView.Adapter { @Override public ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) { } @Override public void onBindViewHolder(final ViewHolder holder, final int position) { } @Override public int getItemCount() { return 0; } public void setItems(@NonNull final ArrayList items) { } } 

In total, nothing came of 4 methods. Immediately the setItems () method catches the eye, it should be able to accept different lists of models, create an empty interface and update the code in the test adapter:

 public interface ItemModel { } public class Test extends RecyclerView.Adapter { @NonNull private final ArrayList<ItemModel> mItems = new ArrayList<>(); .... @Override public int getItemCount() { return mItems.size(); } public void setItems(@NonNull final ArrayList<ItemModel> items) { mItems.clear(); mItems.addAll(items); } } 

Now you need to think of something with onCreateViewHolder () and onBindViewHolder ().

If I want the adapter to be able to bind various views, then it is better if it delegates it to someone. And this will allow the implementation to be reused later. Create an abstract class that will be able to work with only one type of cell and, of course, with a specific ViewHolder. For this we use generics to avoid castes. Let's call it ViewRenderer - nothing more came to mind.

 public abstract class ViewRenderer <M extends ItemModel, VH extends RecyclerView.ViewHolder> { public abstract void bindView(@NonNull M model, @NonNull VH holder); @NonNull public abstract VH createViewHolder(@Nullable ViewGroup parent); } 

Let's try to use it in our adapter. Rename the adapter to something meaningful and modify the code:

 public class RendererRecyclerViewAdapter extends RecyclerView.Adapter { ... private ViewRenderer mRenderer; @Override public RecyclerView.ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) { return mRenderer.createViewHolder(parent); } @Override public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) { mRenderer.bindView(item, holder); } public void registerRenderer(@NonNull final ViewRenderer renderer) { mRenderer = renderer; } ... } 

Looks so far so good. But our adapter must be able to work with several types of views. For this, the adapter has a method getItemViewType (), overriding it in our adapter.

And we will try to ask the cell type from the model itself - we will add a method to the interface and update the adapter method:

 public interface ItemModel { int getType(); } public class RendererRecyclerViewAdapter extends RecyclerView.Adapter { ... @Override public int getItemViewType(final int position) { final ItemModel item = getItem(position); return item.getType(); } private ItemModel getItem(final int position) { return mItems.get(position); } ... } 

At the same time, we will finalize support for several ViewRenderers:

 public class RendererRecyclerViewAdapter extends RecyclerView.Adapter { ... @NonNull private final SparseArray<ViewRenderer> mRenderers = new SparseArray<>(); @Override public RecyclerView.ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) { final ViewRenderer renderer = mRenderers.get(viewType); if (renderer != null) { return renderer.createViewHolder(parent); } throw new RuntimeException("Not supported Item View Type: " + viewType); } public void registerRenderer(@NonNull final ViewRenderer renderer) { final int type = renderer.getType(); if (mRenderers.get(type) == null) { mRenderers.put(type, renderer); } else { throw new RuntimeException("ViewRenderer already exist with this type: " + type); } } @SuppressWarnings("unchecked") @Override public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) { final ItemModel item = getItem(position); final ViewRenderer renderer = mRenderers.get(item.getType()); if (renderer != null) { renderer.bindView(item, holder); } else { throw new RuntimeException("Not supported View Holder: " + holder); } } ... } 

As we can see, the renderer has got the getType () method, this is necessary to find the necessary renderer for a specific view.

Our adapter is ready.

We implement specific classes ItemModel, ViewHolder, ViewRenderer:

Somemodel
 public class SomeModel implements ItemModel { public static final int TYPE = 0; @NonNull private final String mTitle; public SomeModel(@NonNull final String title) { mTitle = title; } @Override public int getType() { return TYPE; } @NonNull public String getTitle() { return mTitle; } ... } 


Someviewholder
 public class SomeViewHolder extends RecyclerView.ViewHolder { public final TextView mTitle; public SomeViewHolder(final View itemView) { super(itemView); mTitle = (TextView) itemView.findViewById(R.id.title); ... } } 


SomeViewRenderer
 public class SomeViewRenderer extends ViewRenderer<SomeModel, SomeViewHolder> { public SomeViewRenderer(final int type, final Context context) { super(type, context); } @Override public void bindView(@NonNull final SomeModel model, @NonNull final SomeViewHolder holder) { ... } @NonNull @Override public SomeViewHolder createViewHolder(@Nullable final ViewGroup parent) { return new SomeViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.some_item, parent, false)); } } 


The ViewRender has a constructor and two parameters for it - ViewRenderer (int viewType, Context context), for which it is necessary, I think, it is not necessary to explain.

Now you can introduce our adapter with RecyclerView:

 public class SomeActivity extends AppCompatActivity { private RendererRecyclerViewAdapter mRecyclerViewAdapter; private RecyclerView mRecyclerView; @Override protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mRecyclerViewAdapter = new RendererRecyclerViewAdapter(); mRecyclerViewAdapter.registerRenderer(new SomeViewRenderer(SomeModel.TYPE, this)); // mRecyclerViewAdapter.registerRenderer(...); mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view); mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); mRecyclerView.setAdapter(mRecyclerViewAdapter); mRecyclerViewAdapter.setItems(getItems()); mRecyclerViewAdapter.notifyDataSetChanged(); } ... } 

Conclusion


We received a working version of the adapter with rather small forces, which can be easily used with several types of cells. To do this, it is enough to implement a ViewRenderer for a specific cell type and register it with our adapter.

At the moment, this implementation has already positively recommended itself in several large projects.

Sample and source code available on the link .

UPD: Easy work with lists - RendererRecyclerViewAdapter (part 2)

Source: https://habr.com/ru/post/323862/


All Articles