📜 ⬆️ ⬇️

Clean Recycler Adapter. Part 1

Introduction




Lists, lists, lists ... Vertical, horizontal, combined. Virtually no mobile application is complete without them. Moreover, applications often consist of lists only.

And if in the “homogeneous” lists there is nothing terrible, then different types of cells can already raise questions, the main of which are:
')

What's wrong with if-else


In general, in “ifakhs” (here are constructions of the form like -else and switch-case ) there is nothing terrible ... as long as they are used for binary choices (for example, the notorious check for null). But when the number of options exceeds two answers, then this is a reason to think about what is wrong with the code and how to fix it.

So why the abundance of choice operators is bad?

Well, firstly, because "ifs" in one place almost always generate "ifs" and in other places of the code. Thus, we obtain from one to infinity (in the limit) editing places. And if you need to make changes, it's very easy to forget to change “one more place”.

Secondly, if you look at the situation again in the limit, then we can get an infinite number of choices. That in the code of the “main” class will look ugly and will become a place of potential (and very likely) errors.

And thirdly, the multitude of “Iphas” is in essence a moveton, a sign that from an architectural point of view, things are not so rosy as it seemed at first glance.

So how can you fix the situation?

There are at least two ways to solve the problem.

First, use the so-called “tabular method” (Steve McConnell “Perfect Code”) and replace the selection logic with a set of prepared data. Which, firstly, eliminates the amount of ugly code, and secondly, allows you to use external sources to provide these same data, thereby eliminating the need to make changes to the code itself.

Secondly, you can use the factory pattern (Banda of Four “Design Patterns”) and encapsulate the logic of choice in one place - in the factory (besides the main duty - hiding the generation of new objects of the same type - the factory can also encapsulate the logic of choice). This does not eliminate the “iphs” completely, as the previous method, but reduces the number of such places to one. Accordingly, the code becomes more beautiful and easy to maintain, because in the case of changes, it will need to be done in exactly one place.

What's wrong with type checking


The type check itself doesn’t mean anything bad. Moreover, looking through the source code even from the largest players in the world of android, I often ran across such checks.

But still a type check, in my opinion, is an omission in architecture (by the way, Scott Myers agrees with me). And if there is an opportunity to get rid of such checks, then it must be done.

How?

The first thing that comes to mind is the familiar “tabular method.” You can, for example, prepare a collection of type map , where you can preassign a type match.

And the second. But there are no such clear recommendations. Everything will depend on the specific case. You can try to use Java Generics where possible. You can look very closely at such a property of the system as polymorphism. As the saying goes, “Interfaces still working everywhere”.

What's wrong with casting


Although the type conversion can be found even in the Android SDK (for example, findViewById () or getSystemService () ), this does not make this procedure safe. Type casting always carries the potential threat of dropping an application by ClassCastException .

Wrapping “castes” in try-catch blocks is not the best way out. First, this design itself looks rather ugly. And secondly, such a problem is quite difficult to catch, because There are no drops, and the application behaves unpredictably.

As an option
A good solution, by the way, is setting up Fabric to send all non-fatal exceptions. There are situations where the labor costs for the “right” solution outweigh the benefits of using it. Therefore, as I have repeatedly repeated, this is an occasion to think about how to fix the situation. And if the decision is too “expensive”, then ... this is a reason to think.

In any case, type casting is not the best choice. And it is better to avoid it.

How?

Of the basic recipes, this is Java Generics and polymorphism. It is also useful to take into account the existence of the Visitor pattern (a gang of four “Design Patterns”).

“Traditional” approach


Deal with the problems. Now let's remember how the task of displaying a “heterogeneous” list is solved in the “traditional” way. “Traditional” is it because, firstly, the Internet prompts us to act this way, and secondly, according to my personal observations, the overwhelming number of juniors and the non-overwhelming number of midls do exactly that.

For example, we have three types of cells:

ProgressVo.java
/** * Just a marker for progress header/footer. */ public class ProgressVo { } 


AdVo.java
 public class AdVo { private String title; private String description; // Getters, Setters, Builder, etc. } 


UserVo.java
 public class UserVo { private String firstName; private String lastName; private String age; // Getters, Setters, Builder, etc. } 


First you need to declare constants for each cell type:

 private static final int TYPE_PROGRESS = 10; private static final int TYPE_AD = 20; private static final int TYPE_USER = 30; 

Further, respectively, you need to determine the cell type for each specific position (in order to avoid hell with the calculation of positions, with arbitrary locations of different types of cells in the list, it is easiest to use an untyped collection):

 @Override public int getItemViewType(int position) { Object item = itemList.get(position); if (item instanceof ProgressVo) { return TYPE_PROGRESS; } else if (item instanceof AdVo) { return TYPE_AD; } else if (item instanceof UserVo) { return TYPE_USER; } else { throw new NoSuchRecyclerItemTypeException(); } } 

And create the appropriate holder:

 @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); if (viewType == TYPE_PROGRESS) { View view = inflater.inflate(R.layout.cell_progress, parent, false); return new UsersRecyclerAdapter.ProgressViewHolder(view); } else if (viewType == TYPE_AD) { View view = inflater.inflate(R.layout.cell_ad, parent, false); return new UsersRecyclerAdapter.AdViewHolder(view); } else if (viewType == TYPE_USER) { View view = inflater.inflate(R.layout.cell_user, parent, false); return new UsersRecyclerAdapter.UserViewHolder(view); } else { throw new NoSuchRecyclerViewTypeException(); } } 

And then also connect our holder with the data:

 @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (holder instanceof ProgressViewHolder) { // Do nothing. } else if (holder instanceof AdViewHolder) { ((AdViewHolder) holder).bind((AdVo) itemList.get(position)); } else if (holder instanceof UserViewHolder) { ((UserViewHolder) holder).bind((UserVo) itemList.get(position)); } } 

The whole class looks like this:

UsersUglyRecyclerAdapter.java
 public class UsersUglyRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private static final int TYPE_PROGRESS = 10; private static final int TYPE_AD = 20; private static final int TYPE_USER = 30; private List itemList = new ArrayList(); public UsersUglyRecyclerAdapter() { itemList.add(new ProgressVo()); } @Override public int getItemViewType(int position) { Object item = itemList.get(position); if (item instanceof ProgressVo) { return TYPE_PROGRESS; } else if (item instanceof AdVo) { return TYPE_AD; } else if (item instanceof UserVo) { return TYPE_USER; } else { throw new NoSuchRecyclerItemTypeException(); } } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); if (viewType == TYPE_PROGRESS) { View view = inflater.inflate(R.layout.cell_progress, parent, false); return new UsersRecyclerAdapter.ProgressViewHolder(view); } else if (viewType == TYPE_AD) { View view = inflater.inflate(R.layout.cell_ad, parent, false); return new UsersRecyclerAdapter.AdViewHolder(view); } else if (viewType == TYPE_USER) { View view = inflater.inflate(R.layout.cell_user, parent, false); return new UsersRecyclerAdapter.UserViewHolder(view); } else { throw new NoSuchRecyclerViewTypeException(); } } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (holder instanceof ProgressViewHolder) { // Do nothing. } else if (holder instanceof AdViewHolder) { ((AdViewHolder) holder).bind((AdVo) itemList.get(position)); } else if (holder instanceof UserViewHolder) { ((UserViewHolder) holder).bind((UserVo) itemList.get(position)); } } @Override public int getItemCount() { return itemList.size(); } public void setUsers(List<UserVo> users) { itemList.clear(); itemList.addAll(users); decorateItemList(); notifyDataSetChanged(); } private void decorateItemList() { int listSize = itemList.size(); int shift = 0; for (int i = 1; i < listSize; i++) { if (i % 7 == 0) { itemList.add(i + shift, new AdVo()); shift++; } } itemList.add(new ProgressVo()); } protected static class ProgressViewHolder extends RecyclerView.ViewHolder { public ProgressViewHolder(View itemView) { super(itemView); } } protected static class AdViewHolder extends RecyclerView.ViewHolder { public AdViewHolder(View itemView) { super(itemView); } public void bind(AdVo ad) { // Bind ad... } } protected static class UserViewHolder extends RecyclerView.ViewHolder { public UserViewHolder(View itemView) { super(itemView); } public void bind(UserVo user) { // Bind user... } } } 


What do we have in the end? Three places with the logic of choice, the abundance of “iofof”, type checks and type conversions. In case of need to make changes, we already have 4 places for this (without taking into account the creation of a new holder when scaling).

As if disorder and all that. Let's understand how to fix the situation.

Cleaner approach


There may be several ways to solve this problem. In this article, we use the adaptation of the “tabular method”, where the “table” will be the enum .

Our goal is to bring the code in a “single line” view:

 @Override public int getItemViewType(int position) { return CellType.get(itemList.get(position)).type(); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return CellType.get(viewType).viewHolder(parent); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { CellType.get(itemList.get(position)).bind(holder, itemList.get(position)); } 

So, first we need to determine the types of cells used:

 private enum CellType { PROGRESS, AD, USER } 

Let's start with what we need to determine the type of cell and create the corresponding holder:

 private enum CellType { PROGRESS { @Override int type() { return R.layout.cell_progress; } @Override RecyclerView.ViewHolder viewHolder(ViewGroup parent) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); View view = inflater.inflate(R.layout.cell_progress, parent, false); return new ProgressViewHolder(view); } }, AD { @Override int type() { return R.layout.cell_ad; } @Override RecyclerView.ViewHolder viewHolder(ViewGroup parent) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); View view = inflater.inflate(R.layout.cell_ad, parent, false); return new AdViewHolder(view); } }, USER { @Override int type() { return R.layout.cell_user; } @Override RecyclerView.ViewHolder viewHolder(ViewGroup parent) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); View view = inflater.inflate(R.layout.cell_user, parent, false); return new UserViewHolder(view); } }; abstract int type(); abstract RecyclerView.ViewHolder viewHolder(ViewGroup parent); } 

I want to note that the cell markup id is used as the viewType . Thus, firstly, there is no need to define constants, and secondly, unique id exclude conflict situations. Some libraries can reserve certain constants for themselves or the current code-base does it. And such things are easily forgotten, which ultimately leads to unpleasant consequences.

Since android SDK in the getItemViewType () and onBindViewHolder () methods uses the item's position in the collection, and in the onCreateViewHolder () method is the viewType variable, then we need two methods to get the corresponding enum :

 private enum CellType { PROGRESS { @Override boolean is(Object item) { return item instanceof ProgressVo; } ... }, AD { @Override boolean is(Object item) { return item instanceof AdVo; } ... }, USER { @Override boolean is(Object item) { return item instanceof UserVo; } ... }; static CellType get(Object item) { for (CellType cellType : CellType.values()) { if (cellType.is(item)) { return cellType; } } throw new NoSuchRecyclerItemTypeException(); } static CellType get(int viewType) { for (CellType cellType : CellType.values()) { if (cellType.type() == viewType) { return cellType; } } throw new NoSuchRecyclerViewTypeException(); } abstract boolean is(Object item); ... } 

The is () method in this case is used only for “internal needs”.

It remains only to associate the holder with the data:

 private enum CellType { PROGRESS { ... @Override void bind(RecyclerView.ViewHolder holder, Object item) { // Do nothing. } }, AD { ... @Override void bind(RecyclerView.ViewHolder holder, Object item) { try { AdViewHolder adViewHolder = (AdViewHolder) holder; AdVo ad = (AdVo) item; adViewHolder.bind(ad); } catch (ClassCastException e) { L.printStackTrace(e); } } }, USER { ... @Override void bind(RecyclerView.ViewHolder holder, Object item) { try { UserViewHolder userViewHolder = (UserViewHolder) holder; UserVo user = (UserVo) item; userViewHolder.bind(user); } catch (ClassCastException e) { L.printStackTrace(e); } } }; ... abstract void bind(RecyclerView.ViewHolder holder, Object item); } 

The resulting class looks like this:

UsersRecyclerAdapter.java
 public class UsersRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private List itemList = new ArrayList(); public UsersRecyclerAdapter() { itemList.add(new ProgressVo()); } @Override public int getItemViewType(int position) { return CellType.get(itemList.get(position)).type(); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return CellType.get(viewType).viewHolder(parent); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { Object item = itemList.get(position); CellType.get(item).bind(holder, item); } @Override public int getItemCount() { return itemList.size(); } public void setUsers(List<UserVo> users) { itemList.clear(); itemList.addAll(users); decorateItemList(); notifyDataSetChanged(); } private void decorateItemList() { int listSize = itemList.size(); int shift = 0; for (int i = 1; i < listSize; i++) { if (i % 7 == 0) { itemList.add(i + shift, new AdVo()); shift++; } } itemList.add(new ProgressVo()); } private enum CellType { PROGRESS { @Override boolean is(Object item) { return item instanceof ProgressVo; } @Override int type() { return R.layout.cell_progress; } @Override RecyclerView.ViewHolder viewHolder(ViewGroup parent) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); View view = inflater.inflate(R.layout.cell_progress, parent, false); return new ProgressViewHolder(view); } @Override void bind(RecyclerView.ViewHolder holder, Object item) { // Do nothing. } }, AD { @Override boolean is(Object item) { return item instanceof AdVo; } @Override int type() { return R.layout.cell_ad; } @Override RecyclerView.ViewHolder viewHolder(ViewGroup parent) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); View view = inflater.inflate(R.layout.cell_ad, parent, false); return new AdViewHolder(view); } @Override void bind(RecyclerView.ViewHolder holder, Object item) { try { AdViewHolder adViewHolder = (AdViewHolder) holder; AdVo ad = (AdVo) item; adViewHolder.bind(ad); } catch (ClassCastException e) { L.printStackTrace(e); } } }, USER { @Override boolean is(Object item) { return item instanceof UserVo; } @Override int type() { return R.layout.cell_user; } @Override RecyclerView.ViewHolder viewHolder(ViewGroup parent) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); View view = inflater.inflate(R.layout.cell_user, parent, false); return new UserViewHolder(view); } @Override void bind(RecyclerView.ViewHolder holder, Object item) { try { UserViewHolder userViewHolder = (UserViewHolder) holder; UserVo user = (UserVo) item; userViewHolder.bind(user); } catch (ClassCastException e) { L.printStackTrace(e); } } }; static CellType get(Object item) { for (CellType cellType : CellType.values()) { if (cellType.is(item)) { return cellType; } } throw new NoSuchRecyclerItemTypeException(); } static CellType get(int viewType) { for (CellType cellType : CellType.values()) { if (cellType.type() == viewType) { return cellType; } } throw new NoSuchRecyclerViewTypeException(); } abstract boolean is(Object item); abstract int type(); abstract RecyclerView.ViewHolder viewHolder(ViewGroup parent); abstract void bind(RecyclerView.ViewHolder holder, Object item); } protected static class ProgressViewHolder extends RecyclerView.ViewHolder { public ProgressViewHolder(View itemView) { super(itemView); } } protected static class AdViewHolder extends RecyclerView.ViewHolder { public AdViewHolder(View itemView) { super(itemView); } public void bind(AdVo ad) { // Bind ad... } } protected static class UserViewHolder extends RecyclerView.ViewHolder { public UserViewHolder(View itemView) { super(itemView); } public void bind(UserVo user) { // Bind user... } } } 


A little more “purity”


Alternatively, type checking can be replaced by another “tabular method”. You can use the map collection to check the type match.

Remove the is () method and initialize the corresponding map collection:

 private enum CellType { ... static Map<Class, CellType> typeTable = new HashMap<>(); static { typeTable.put(ProgressVo.class, PROGRESS); typeTable.put(AdVo.class, AD); typeTable.put(UserVo.class, USER); } static CellType get(Object item) { return typeTable.get(item.getClass()); } … } 

This approach should be considered as an alternative. Those. This is such a half-measure (the question was solved with a type check, but did not affect the type conversion), which also simplifies the enum contract.

What does it threaten with?

And the fact that it is possible in the heat of battle to make changes is very easy to forget about this typeTable and get NPE .

Supporting the “full” contract (this is the is () method), this situation is excluded.

Conclusion


So what we got at the exit?

Begin with this:

Ugly Adapter
  private static final int TYPE_PROGRESS = 10; private static final int TYPE_AD = 20; private static final int TYPE_USER = 30; @Override public int getItemViewType(int position) { Object item = itemList.get(position); if (item instanceof ProgressVo) { return TYPE_PROGRESS; } else if (item instanceof AdVo) { return TYPE_AD; } else if (item instanceof UserVo) { return TYPE_USER; } else { throw new NoSuchRecyclerItemTypeException(); } } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); if (viewType == TYPE_PROGRESS) { View view = inflater.inflate(R.layout.cell_progress, parent, false); return new UsersRecyclerAdapter.ProgressViewHolder(view); } else if (viewType == TYPE_AD) { View view = inflater.inflate(R.layout.cell_ad, parent, false); return new UsersRecyclerAdapter.AdViewHolder(view); } else if (viewType == TYPE_USER) { View view = inflater.inflate(R.layout.cell_user, parent, false); return new UsersRecyclerAdapter.UserViewHolder(view); } else { throw new NoSuchRecyclerViewTypeException(); } } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (holder instanceof ProgressViewHolder) { // Do nothing. } else if (holder instanceof AdViewHolder) { ((AdViewHolder) holder).bind((AdVo) itemList.get(position)); } else if (holder instanceof UserViewHolder) { ((UserViewHolder) holder).bind((UserVo) itemList.get(position)); } } 


And they came to this:

Clean adapter
  @Override public int getItemViewType(int position) { return CellType.get(itemList.get(position)).type(); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return CellType.get(viewType).holder(parent); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { Object item = itemList.get(position); CellType.get(item).bind(holder, item); } private enum CellType { PROGRESS { @Override boolean is(Object item) { return item instanceof ProgressVo; } @Override int type() { return R.layout.cell_progress; } @Override RecyclerView.ViewHolder holder(ViewGroup parent) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); View view = inflater.inflate(R.layout.cell_progress, parent, false); return new ProgressViewHolder(view); } @Override void bind(RecyclerView.ViewHolder holder, Object item) { // Do nothing. } }, AD { @Override boolean is(Object item) { return item instanceof AdVo; } @Override int type() { return R.layout.cell_ad; } @Override RecyclerView.ViewHolder holder(ViewGroup parent) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); View view = inflater.inflate(R.layout.cell_ad, parent, false); return new AdViewHolder(view); } @Override void bind(RecyclerView.ViewHolder holder, Object item) { try { AdViewHolder adViewHolder = (AdViewHolder) holder; AdVo ad = (AdVo) item; adViewHolder.bind(ad); } catch (ClassCastException e) { L.printStackTrace(e); } } }, USER { @Override boolean is(Object item) { return item instanceof UserVo; } @Override int type() { return R.layout.cell_user; } @Override RecyclerView.ViewHolder holder(ViewGroup parent) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); View view = inflater.inflate(R.layout.cell_user, parent, false); return new UserViewHolder(view); } @Override void bind(RecyclerView.ViewHolder holder, Object item) { try { UserViewHolder userViewHolder = (UserViewHolder) holder; UserVo user = (UserVo) item; userViewHolder.bind(user); } catch (ClassCastException e) { L.printStackTrace(e); } } }; static CellType get(Object item) { for (CellType cellType : CellType.values()) { if (cellType.is(item)) { return cellType; } } throw new NoSuchRecyclerItemTypeException(); } static CellType get(int viewType) { for (CellType cellType : CellType.values()) { if (cellType.type() == viewType) { return cellType; } } throw new NoSuchRecyclerViewTypeException(); } abstract boolean is(Object item); abstract int type(); abstract RecyclerView.ViewHolder holder(ViewGroup parent); abstract void bind(RecyclerView.ViewHolder holder, Object item); } 


Let's walk on the questions indicated at the beginning of the article.


We have exactly one place to make changes to existing cells (under the rapidly changing customer desires) and to add new ones. Moreover, when adding a new type of cell it is possible that we will forget something, because required to maintain the current contract enum. Simple and safe.


The cumbersome and ugly logic of choice is no longer there. No, and three places where this logic was used. The risk of error in this regard is excluded. Yes, and colleagues now do not laugh.


This question has partially remained open in this article. As they say, not all at once.

Three and a half of the four questions are resolved. So why did I suggest this method if it does not solve all the questions?

Well, firstly, the information on one article and so in abundance.

Secondly, this approach solves most issues and simplifies code development and maintenance.

And thirdly, this method is very simple, concise and fast. Those. The balance of labor is the benefit here, which is definitely on the benefit side. And we get a considerable benefit.

Yes, the topic looks somewhat holistic and ambiguous and can cause controversy. But since Since the lists take almost a key role in mobile applications, and the Android SDK does not provide a beautiful way to work with different types of cells out of the box, then I considered it necessary to share one of the good ways to solve this problem.

See you in the second part, where we will talk about how to get rid of type checks and type casting.

Update


Forced to write a small explanation of the article, because In the comments I met some misunderstanding. The article presents an idea , shows the approach to solving the problem. As simple as possible, as simple as possible (one class, enum, explicit local variables, etc.).

The implementation of this idea may be different, based on personal preferences. But, as practice has shown, people are waiting for a ready-made solution from the article. Those. not a problem solving method , but a concrete implementation of this method.

Well, take note.

In the comments, comrade r_ii turned on the brain and showed his realization of the idea. Below I will share my (in order to anticipate the next wave of possible questions I will immediately include in the code and the option of processing clicks).

My realization of the idea
 public class UsersArbitraryCellAdapter extends ArbitraryCellAdapter { private ProgressArbitraryCell progressArbitraryCell = new ProgressArbitraryCell(); private AdArbitraryCell adArbitraryCell = new AdArbitraryCell(); private UserArbitraryCell userArbitraryCell = new UserArbitraryCell(); public UsersArbitraryCellAdapter() { arbitraryCellSelector.addCell(progressArbitraryCell); arbitraryCellSelector.addCell(adArbitraryCell); arbitraryCellSelector.addCell(userArbitraryCell); } public Observable<AdVo> asAdObservable() { return adArbitraryCell.asAdObservable(); } public Observable<UserVo> asUserObservable() { return userArbitraryCell.asUserObservable(); } // Set Users, Ads, Progress... } public abstract class ArbitraryCellAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { protected ArbitraryCellSelector arbitraryCellSelector = new ArbitraryCellSelector(); protected List itemList = new ArrayList(); @Override public final int getItemViewType(int position) { return arbitraryCellSelector.getCell(itemList.get(position)).type(); } @Override public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return arbitraryCellSelector.getCell(viewType).holder(parent); } @Override public final void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { Object item = itemList.get(position); arbitraryCellSelector.getCell(item).bind(holder, item); } @Override public final int getItemCount() { return itemList.size(); } } public abstract class ArbitraryCellHolder<T> extends RecyclerView.ViewHolder { public ArbitraryCellHolder(View itemView) { super(itemView); ButterKnife.bind(this, itemView); } public abstract void bind(T item); } public final class ArbitraryCellSelector { private List<Cell> cellList = new ArrayList<>(); public void addCell(Cell cell) { cellList.add(cell); } public void removeCell(Cell cell) { cellList.remove(cell); } public Cell getCell(Object item) { for (Cell cell : cellList) { if (cell.is(item)) { return cell; } } throw new NoSuchRecyclerRowException(); } public Cell getCell(int viewType) { for (Cell cell : cellList) { if (cell.type() == viewType) { return cell; } } throw new NoSuchRecyclerRowException(); } public interface Cell { boolean is(Object item); int type(); RecyclerView.ViewHolder holder(ViewGroup parent); void bind(RecyclerView.ViewHolder holder, Object item); } } public class AdArbitraryCell implements ArbitraryCellSelector.Cell { private PublishSubject<AdVo> adPublishSubject = PublishSubject.create(); @Override public boolean is(Object item) { return item instanceof AdVo; } @Override public int type() { return R.layout.cell_ad; } @Override public RecyclerView.ViewHolder holder(ViewGroup parent) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); View view = inflater.inflate(R.layout.cell_ad, parent, false); return new AdViewHolder(view); } @Override public void bind(RecyclerView.ViewHolder holder, Object item) { try { AdViewHolder adViewHolder = (AdViewHolder) holder; AdVo ad = (AdVo) item; adViewHolder.bind(ad); } catch (ClassCastException e) { L.printStackTrace(e); } } public Observable<AdVo> asAdObservable() { return adPublishSubject; } protected class AdViewHolder extends ArbitraryCellHolder<AdVo> { @BindView(R.id.ad_text_view) protected TextView adTextView; public AdViewHolder(View itemView) { super(itemView); } @Override public void bind(AdVo item) { adTextView.setText(item.getTitle()); itemView.setOnClickListener(view -> adPublishSubject.onNext(item)); } } } // Other arbitrary cells... 


Comrade zagayevskiy drew attention to the library of Hannes Dorfmann, which is called AdapterDelegates. The approach looks solid, the solution is elegant. Recommend.

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


All Articles