To hell with adapters, with Smart Adapters

To hell with adapters, with Smart Adapters

What I have dreaded for ages in Android was having to deal with Adapters in my ListViews. The code is always boring and pretty much the same all the time, and whenever it feels different, it means that the cells are getting too complicated... the adapter's LOC grow more and more, and it becomes unmanageable. RecyclerView doesn't help with that either.

Some time ago, back in Mobivery, we developed a tiny helper library that contained some solution around this: we would use a generic adapter for mapping some model classes to some view classes. It saved us many dev hours and proved very handy. And after I left, I developed my own take based on that principle and published it in nl-toolkit. I used it for a couple of my projects last year, and worked perfectly, so I took some time and rebuilt it with a nicer API and extracted it to a new library.

I called it Smart Adapters.

The concept is simple: we create all the interaction based code within custom view classes, inheriting from a class called BindableLayout<YourModelHere> and then we create the adapter, assign it to a ListView/RecyclerView and map the model classes to the view classes. That's it.

SmartAdapter.items(myObjectList).map(Tweet.class, TweetView.class).into(myListView);  

We can even pass a list of many different objects to the items call and map different objects to their views, so we can render a list with multiple row classes effortlessly.

SmartAdapter.items(myObjectList)       .map(Tweet.class, TweetView.class)     .map(String.class, ListHeaderView.class)     .map(User.class, UserView.class)     .into(myListView);

One cool perk this library offers is that all widgets inheriting from AbsListView (namely ListView and GridView) and RecyclerView are compatible with the library, and use the exact same API. The generated adapters might be different, but the code you have to type is the same. This way you can pretty much change some list to use RecyclerView instead of ListView (and viceversa). This has proven very useful to me when updating legacy codebases that already used this library for ListView, so they would use RecyclerView instead.

The view classes instantiation uses reflection, but caches the methods, so the penalty on performance is negligible.

The custom view classes usually look like this:

public class TweetView extends BindableLayout<Tweet> {      // ButterKnife injections     @InjectView(R.id.tweet_text) TextView tweetText;     @InjectView(R.id.author_text) TextView authorText;      public TweetView(Context context) {         // This is the constructor that should be implemented, because it's the one used internally         // by the default builder.         super(context);     }      @Override     public int getLayoutId() {         // This is mandatory, and should return the id for the view layout of this view         return R.layout.view_tweet;     }      @Override     public void onViewInflated() {         // Here we should assign the views or use ButterKnife         ButterKnife.inject(this);     }      @Override     public void bind(Tweet tweet) {         // In here we assign the model information values to our view widgets          // Examples:         tweetText.setText(tweet.getText());         authorText.setText(tweet.getAuthor());          // and so on!     } }

For capturing interactions and propagating them to the SmartAdapter caller class, there is a ViewEventListener interface. We can propagate all the events we want, so we could use the same callback for row tap, row long-press, some button inside the row tap, etc.

Anyway, you got a couple of examples of use in the sample directory on GitHub so please check it out!

For more documentation about this and other more advance features (like builders) please take a look to the project README file.