Drebin, a binder library
Drebin is a binder library for Android's RecyclerView. The name comes from the Lt. Frank Drebin. First, because Leslie Nielsen is godlike. And second, because Drebin is an anagram for Binder.
The binders are defined as stateless templates, can be easily injected if you are using a DI framework, and they are easy to composite.
Why
I built it to leave behind some of the problems I was facing with SmartAdapters. This could be considered the new version for it; I built it from scratch and they are completely different approaches, so it made sense to keep them separated.
The caveat I was finding when working with SmartAdapters was all the heavy lifting involved in working with BindableLayouts. As we were working with custom views, and didn't have access to the adapter, using an inflated layout was bothersome if you wanted to get rid of the 1st layer in the view hierarchy. So you had to do things like setting orientation in a LinearLayout programmatically, and working with <merge/>
tags. Which is uncomfortable as hell.
With this approach the Binder, which roughly serves the same purpose as a BindableLayout, is apart from the view and is easily injectable. Also, it's easy to do composition between binders. And using BinderEnvironments is pretty flexible for things like multiple actions when tapping in different views. The environments and the selectors are easily injectable as well.
And we get rid of all the reflection magic of the builders, which is nice as well.
So, how?
The basics are: you create a binder for populating a View and you call the fluid api on the Drebin class.
So, the basic plumbing would be something like this:
Drebin.with(getActivity())
.items(mItems)
.environment(mHasClickableChampion)
.binder(ChampionRedux.class, mChampionBinder)
.into(mRecyclerView);
The binder basic interface is like this:
public interface Binder<V extends View, VH extends ViewHost<V>, M, BE extends BinderEnvironment> {
ViewFactory<V> getViewFactory();
VH createViewHost(V view);
void bind(M model, VH viewHost, BE environment);
void unbind(VH viewHost, BE environment);
}
An example implementation from SalsaLoL:
The ChampionViewHost in here is not really necessary, you could just use ViewHost directly instead. But you would have to access the view on the bind(...) method, which is pretty bothersome.
When you want to spice things up: BinderSelectors
In situations where you have different renderings for the same model object class, BinderSelectors come to the rescue. They will allow you to decide which Binder to be used for a particular model.
public interface BinderSelector<M, BE extends BinderEnvironment> {
Binder select(M model, BE environment);
}
Being binders templates, how can we get to the complex stuff?
If you want to let the binder know about more stuff going on with your app apart from the model itself, you might want to create your own BinderEnvironment. It works both ways, from the app to the row being bound, and from the row being bound to the app.
The most typical case would be using the environment for receiving information triggered by user actions on your bound view, like a click or a long press.
An example interface could be:
public interface HasUsersEnvironment extends BinderEnvironment {
boolean isCurrentUser(User user);
void userSelected(User user);
}
I usually build a lot of different traits and create a common implementation for a bunch of them at the same time.
An incomplete example, you can infer what the interfaces do easily:
public class CoverEnvironment implements BinderEnvironment, HasContext, HasClickableChampion, HasClickableGuide, HasClickableNews, HasClickableVideo {
@Inject HasClickableChampionImpl mHasClickableChampionImpl;
@Inject HasClickableGuideImpl mHasClickableGuideImpl;
@Inject HasContextImpl mHasContextImpl;
private Listener mListener;
@Inject
public CoverEnvironment() {
}
public void setListener(Listener listener) {
mListener = listener;
mHasClickableChampionImpl.setListener(listener);
mHasClickableGuideImpl.setListener(listener);
}
@Override
public void championClicked(final ChampionBasic champion) {
mHasClickableChampionImpl.championClicked(champion);
}
@Override
public void guideClicked(final GuideBasic guide) {
mHasClickableGuideImpl.guideClicked(guide);
}
@Override
public void newsClicked(final CoverItem.News news) {
if (mListener != null) {
mListener.newsClicked(news);
}
}
@Override
public void videoClicked(final CoverItem.Video video) {
if (mListener != null) {
mListener.videoClicked(video);
}
}
@Override
public Context getContext() {
return mHasContextImpl.getContext();
}
public interface Listener extends HasClickableChampionImpl.OnChampionClickedListener, HasClickableGuideImpl.OnGuideClickedListener {
void newsClicked(CoverItem.News news);
void videoClicked(CoverItem.Video video);
}
}
This way we can obtain things that we might need for the environment (like a particular context), or we can give back control to the activity/fragment after some user action in our items at the list (all the xxxClicked stuff).
So...
Well, just play around with it, use it if you see it fit. I am surely doing it!