📜 ⬆️ ⬇️

Android Data Binding for RecyclerView: flexible way



Much time has passed since the first announcement on Google IO 2015 of the new Data Binding Library . There were many examples, many guides and many fixes and improvements in the library itself. Binding has already become a two-way, and you can refer to other View by their id in the layout file itself and the army of fans of this library is growing steadily. And, probably, each new adept begins by searching for examples - how to use it correctly so that it is convenient, and less code, and in Feng Shui. If you now drive a query on the similarity of “Android DataBinding + RecyclerView”, then, for sure, we will get a whole bunch of links to various guides. Even on Habré there was already a similar article - Android Data Binding in RecyclerView .

But despite such an abundance of resources / guides, many of them show the basic functionality, and each developer, starting to actively use Data Binding, comes up with his own way of working that is convenient for him. Next will be shown one of these methods.
Example here: DataBinding_For_RecyclerView

Stages:
')
- implementation / customization of the Adapter (viewTypes, items, handling clicks on elements and within the list elements themselves);
- setting RecyclerView (set LayoutManager, Adapter, ItemDecorator, ItemAnimator, item divider size, ScrollListener, ...).

Configuring RecyclerView


Let's leave the adapter implementation for now and consider the way to configure the RecyclerView itself. The easiest way here is to simply assign an id to RecyclerView and set all parameters in the code already:

mBinding.recyclerView.setLayoutManager(new LinearLayoutManager(context)); // +  . 

The second, often met, way is to make a part of the most trivial initializations in the code, for example, like this:

 <android.support.v7.widget.RecyclerView app:layoutManager="android.support.v7.widget.GridLayoutManager" /> 

Finally, the method used by the author is to use an intermediary class that will be configured in the code and applied through binding. The profit of this approach is the ability to hide the “default” settings inside the mediation class (helper), but still have full control over the configuration of RecyclerView in one place.

 <android.support.v7.widget.RecyclerView app:listConfig="@{viewModel.listConfig}"/> 

In the code:

 ListConfig listConfig = new ListConfig.Builder(mAdapter) //.setLayoutManagerProvider(new GridLayoutManagerProvider(mCount, mSpanLookup)) //LinearLayoutManager if not set //.addItemDecoration(new ColorDividerItemDecoration(color, spacing, SPACE_LEFT | SPACE_TOP, false)) .setDefaultDividerEnabled(true) .addOnScrollListener(new OnLoadMoreScrollListener(mCallback)) .setItemAnimator(getItemAnimator()) .setHasFixedSize(true) .setItemTouchHelper(getItemTouchHelper()) .build(context); 


What ListConfig is
 public class ListConfig { // Adapter, LayoutManager, ItemAnimator, ItemDecorations, ScrollListeners, // ItemTouchHelper, hasFixedSize private ListConfig(/*params*/) { // init fields } public void applyConfig(final Context context, final RecyclerView recyclerView) { //... apply config } public static class Builder { public Builder(Adapter adapter) {/*set field*/} public Builder setLayoutManagerProvider(LayoutManagerProvider layoutManagerProvider){/*set field*/} public Builder setItemAnimator(ItemAnimator itemAnimator){/*set field*/} public Builder addItemDecoration(ItemDecoration itemDecoration){/*set field*/} public Builder addOnScrollListener(OnScrollListener onScrollListener){/*set field*/} public Builder setHasFixedSize(boolean isFixedSize){/*set field*/} public Builder setDefaultDividerEnabled(boolean isEnabled){/*set field*/} public Builder setDefaultDividerSize(int size){/*set field*/} public Builder setItemTouchHelper(ItemTouchHelper itemTouchHelper){/*set field*/} public ListConfig build(Context context) { /*set default values*/ return new ListConfig(/*params*/); } } public interface LayoutManagerProvider { LayoutManager get(Context context); } } 


Implementing a flexible adapter


One of the most interesting questions is inheritance or composition. Many repeat as the mantra “Prefer composition to inheritance”, but still continue to produce heirs from heirs from heirs ... Whoever is not familiar with the terrific article on this topic as applied to list adapters, be sure to review JOE'S GREAT ADAPTER HELL ESCAPE . In short, let's imagine this situation: we are given the task to implement a simple application with 2 lists: the list of users (User) and the list of locations (Location). Nothing complicated, right?) We create two adapters, - UserAdapter and LocationAdapter, - and, in fact, everything. But here, in the next “sprint” (we are in Agile, right?), The customer also wants to add advertising to each of these lists (Advertisment).

 public class User implements BaseModel { public String name; public String avatar; } public class Location implements BaseModel { public String name; public String image; } public class Advertisement implements BaseModel { public String label; public String image; } 

No problem, we say, create another AdvertismentAdapter and inherit from both the previous ones: UserAdapter extends AdvertismentAdapter and LocationAdapter extends AdvertismentAdapter. Everything is good, everyone is happy, but in the new “sprint” the client wants another list where all 3 entities will be mixed at once. How to be now?

And here we go from inheritance to composition. Before that, we had a separate adapter for each list with its own types (viewTypes), now we will replace this system with one adapter and 3 delegates for each type of list item. The adapter will not know anything about the types of elements that it displays, but it knows that it has several delegates, asking each of them in turn to find a specific item for the list item and delegate to it the creation of this element.

Adapter on delegates


In this case, we already do not care how many lists and with what types of elements there will be, any list is formed as a constructor - by a set of delegates.

 mAdapter = new DelegateAdapter<>( new UserDelegate(actionHandler), // or new ModelItemDelegate(User.class, R.layout.item_user, BR.user), new LocationDelegate(), new AdvertismentDelegate(), // ... ); 

Example delegate implementation (UserDelegate)
 public class UserDelegate extends ActionAdapterDelegate<BaseModel, ItemUserBinding> { public UserDelegate(final ActionClickListener actionHandler) { super(actionHandler); } @Override public boolean isForViewType(@NonNull final List<BaseModel> items, final int position) { return items.get(position) instanceof User; } @NonNull @Override public BindingHolder<ItemUserBinding> onCreateViewHolder(final ViewGroup parent) { return BindingHolder.newInstance(R.layout.item_user, LayoutInflater.from(parent.getContext()), parent, false); } @Override public void onBindViewHolder(@NonNull final List<BaseModel> items, final int position, @NonNull final BindingHolder<ItemUserBinding> holder) { final User user = (User) items.get(position); holder.getBinding().setUser(user); holder.getBinding().setActionHandler(getActionHandler()); } @Override public long getItemId(final List<BaseModel> items, final int position) { return items.get(position).getId(); } } 


As for DataBinding, all the magic is in a special ViewHolder:

 public class BindingHolder<VB extends ViewDataBinding> extends RecyclerView.ViewHolder { private VB mBinding; public static <VB extends ViewDataBinding> BindingHolder<VB> newInstance( @LayoutRes int layoutId, LayoutInflater inflater, ViewGroup parent, boolean attachToParent) { final VB vb = DataBindingUtil.inflate(inflater, layoutId, parent, attachToParent); return new BindingHolder<>(vb); } public BindingHolder(VB binding) { super(binding.getRoot()); mBinding = binding; } public VB getBinding() { return mBinding; } } 

If, however, even laziness to create a separate delegate for each new type / type of list item, you can use the binding feature and use a single universal delegate for any type:

  // new UserDelegate(actionHandler), new ModelItemDelegate(User.class, R.layout.item_user); // or new ModelItemDelegate(R.layout.item_user, BR.model, (item) -> item instance of User); 

ModelItemDelegate
 public class ModelItemDelegate<T> extends BaseListBindingAdapterDelegate<T, ViewDataBinding> { private final int mModelId; private final int mItemLayoutResId; private final ViewTypeClause mViewTypeClause; public ModelItemDelegate(@NonNull Class<? extends T> modelClass, @LayoutRes int itemLayoutResId) { this(itemLayoutResId, BR.model, new SimpleViewTypeClause(modelClass)); } public ModelItemDelegate(@LayoutRes int itemLayoutResId, int modelId, ViewTypeClause viewTypeClause) { mItemLayoutResId = itemLayoutResId; mViewTypeClause = viewTypeClause; mModelId = modelId != 0 ? modelId : BR.model; } @Override public boolean isForViewType(@NonNull List<T> items, int position) { return mViewTypeClause.isForViewType(items, position); } @NonNull @Override public BindingHolder<ViewDataBinding> onCreateViewHolder(ViewGroup parent) { return BindingHolder.newInstance(mItemLayoutResId, LayoutInflater.from(parent.getContext()), parent, false); } @Override public void onBindViewHolder(@NonNull List<T> items, int position, @NonNull BindingHolder<ViewDataBinding> holder) { ViewDataBinding binding = holder.getBinding(); binding.setVariable(mModelId, items.get(position)); binding.executePendingBindings(); } public interface ViewTypeClause { boolean isForViewType(List<?> items, int position); } public static class SimpleViewTypeClause implements ViewTypeClause { private final Class<?> mClass; public SimpleViewTypeClause(@NonNull Class<?> aClass) { mClass = aClass; } @Override public boolean isForViewType(List<?> items, int position) { return mClass.isAssignableFrom(items.get(position).getClass()); } } } 


It is easy to implement the processing of clicks on elements by transferring a click handler through the binding, for example, as described here - Android and Data Binding: processing actions , or using any other method convenient for you.

Conclusion


Thus, using the Android Data Binding Library, the implementation of lists becomes a completely trivial thing. You do not even need to write the implementation of the things shown above, but simply by importing the author's ready-made library, or simply by copying them from there.

Example library: DataBinding_For_RecyclerView

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


All Articles