The Search for One Recycler View Adapter for All*

So I call items in a recycler view cards. I got this idea from how basic email works: You always need a to,subject,body etc. Why cant we do the same for cards in a recycler view?


The recycler view was one of the best additions to Android. The separation between the Layout Manager and the Adapter itself gave us the opportunity to switch to vertical , horizontal , grid or staggered layout view any time we wanted to. It was great for animations and by default it implemented the View Holder Pattern which made sure that we could populate it with large amounts of data and we would still be fine.

But have you come across the situation where you are writing an app and you begin to have so many Recycler View Adapters running all over the place. Imagine you were building a small chat application for a school. And you wanted to have an activity for teachers,students,groups,courses etc.

Usually you do your brainstorming and your smart brain goes like “You need to display the data in a list using the recycler view …” . So yo do that for teachers, then you do the same for students then groups then courses and you find out your whole project is made up of Recycler View Adapters like so

StudentRecyclerAdapter.java, TeachersRecyclerAdapter.java,GroupsRecyclerAdapter.java,CoursesRecyclerAdapter.java …

What do you need to show in a list? You just wish to show data in a presentable way right. So you might have different xmls with different id’s and everything but the Adapter should not change since it is only meant to render views. Lets take a look at how we can create one adapter for all.

Lets Start

Just follow me for a bit and you will get where I am going with this. First we need to create these classes

public CardObject(String title, String subtitle, String image, String info){
this.title = title;
this.subtitle = subtitle;
this.image = image;
this.info = info;
}

So I call items in a recycler view cards. I got this idea from how basic email works: You always need a to,subject,body etc. Why cant we do the same for cards in a recycler view? Every card has something of the above or? So how does the xml look like? Well just like

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:id="@+id/container"
>


<ImageView
android:id="@+id/image"
android:layout_width="30dp"
android:layout_height="30dp"
/>
            <TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>

<TextView
android:id="@+id/info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>

<TextView
android:id="@+id/subtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>

<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="@color/light_grey"
android:id="@+id/border"
/>

</LinearLayout>

Just a simple xml right. Lets move on.

public CardListPackage(String cardType, ArrayList<CardObject> cardObjects, int cardXML) {
this.cardType = cardType; //type of data eg students,courses etc
this.cardObjects = cardObjects; //data for the recycler view
this.cardXML = cardXML; //xml associated to this card type
}

Well lets move on so lets take a first look at our recycler view adapter.

public CardObjectAdapter(Context context, CardListPackage cardListPackage,CardBindingListener cardBindingListener, CardTappedListener cardTappedListener) {
this.context = context;
this.cardObjects = cardListPackage.getCardObjects();
this.card_xml = cardListPackage.getCardXML();
this.cardType = cardListPackage.getCardType();

this.cardTappedListener = cardTappedListener;
this.cardBindingListener = cardBindingListener;
}
@Override
public CardObjectViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(card_xml, viewGroup, false);
CardObjectViewHolder cardObjectViewHolder = new CardObjectViewHolder(view,cardType);
return cardObjectViewHolder;
}
@Override
public void onBindViewHolder(final CardObjectViewHolder cardObjectViewHolder, final int position) {

cardObjectViewHolder.cardObject = cardObjects.get(position);
final CardObject cardObject = cardObjects.get(position);
try {
cardObjectViewHolder.title.setText(cardObject.getTitle());
cardObjectViewHolder.subtitle.setText(cardObject.getSubtitle());
cardObjectViewHolder.info.setText(cardObject.getInfo());
} catch (Exception e) {
Log.e("error", e.toString());
}

//
cardBindingListener.onCardBinding(cardObject,cardObjectViewHolder,position);
}
@Override
public int getItemCount() {
return cardObjects.size();
}
public class CardObjectViewHolder extends RecyclerView.ViewHolder {


//simple views
public TextView title, subtitle, info;
public ImageView image;
public CardObject cardObject;
public int position;
    //you can put more views here
public View border,parentCard;
    public CardObjectViewHolder(final View card,String cardType) {
super(card);

parentCard = card;
title = (TextView) parentCard.findViewById(R.id.title);
image = (ImageView) parentCard.findViewById(R.id.image);
subtitle = (TextView) parentCard.findViewById(R.id.subtitle);
info = (TextView) parentCard.findViewById(R.id.info);

cardObjectPresenter = new CardObjectPresenter(context,cardType,this);

cardObjectPresenter.findCardReferences();

card.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
cardTappedListener.onCardTapped(cardObject);
}
});
}
}

So whats happening is that we have a class that extends the RecyclerView.Adapter class and implements the necessary interfaces. But it does not associate itself to any particular list type eg student list,teachers list or whatever list. It is made in such a way that it should handle any list that you pass if and only if that list is of type CardObject.

So someone will say you have to transform your ArrayList<Student> to ArrayList<CardObject>. Well yeah, but its a small sacrifice.

Now you are asking what in the world is CardObjectPresenter: Well its a fancy name I gave to this class

public CardObjectPresenter(Context context, String cardType, CardObjectAdapter.CardObjectViewHolder cardObjectViewHolder) {
this.context = context;
this.cardObjectViewHolder = cardObjectViewHolder;
this.cardType = cardType;
}
//used to find extra references apart from the basic //title,subtitle,image and info
public void findCardReferences() {
switch (cardType.toLowerCase()) {
case "students":
cardObjectViewHolder.border = cardObjectViewHolder.parentCard.findViewById(R.id.border);
break;
       //more cases here ...
}
}

This just prevents the Adapter classes by bloating up.

So you saw CardBindingListener cardBindingListener and CardTappedListener cardTappedListener in the constructor definition and you were wondering what they were, right. Well they are simply

public interface CardBindingListener {
void onCardBinding(CardObject cardObject, CardObjectAdapter.CardObjectViewHolder cardObjectViewHolder,int position);
}

So these two are interfaces that are implemented when instantiating the CardObjectAdapter. The onCardBinding is called in the onBindViewHolder of the view holder method. It will set other values that are not present in the basic values(title,subtitle,image,info). So say you had a Fees Page and you want to show a list of paid fees and there is one view say charges which is not basic you could set it here.

public interface CardTappedListener {
public void onCardTapped(CardObject cardObject);
}

Now lets see how all this works out. So lets say I have created my Students Activity (Students.java) and I want to use our CardObjectAdapter. What do I do? Well I just do this

final int cardSize = cardObjects.size();
CardListPackage cardListPackage = new CardListPackage("students",cardObjects,R.layout.student_card);
cardObjectAdapter = new CardObjectAdapter(context, cardListPackage, new CardBindingListener() {
@Override
public void onCardBinding(CardObject cardObject, CardObjectAdapter.CardObjectViewHolder cardObjectViewHolder, int position) {
if (position == cardSize - 1) {
//the extra view for the student card    cardObjectViewHolder.border.setBackgroundColor(context.getResources().getColor(R.color.white));
}
Picasso.with(context).load(cardObject.getImage()).error(R.drawable.app_icon_gray).into(cardObjectViewHolder.image);
}
}, new CardTappedListener() {
@Override
public void onCardTapped(CardObject cardObject) {
Intent intent = new Intent(context, StudentProfile.class);
intent.putExtra("card_id", cardObject.getId());
intent.putExtras(cardObject.getCardBundle());
context.startActivity(intent);
}
});

Say I have one for teachers , what do I do ? Well pretty much the same thing

CardListPackage cardListPackage = new CardListPackage("teachers",cardObjects,R.layout.teacher_card);
cardObjectAdapter = new CardObjectAdapter(context, cardListPackage, new CardBindingListener() {
@Override
public void onCardBinding(CardObject cardObject, CardObjectAdapter.CardObjectViewHolder cardObjectViewHolder, int position) {
//there is no extra view for this one
Picasso.with(context).load(cardObject.getImage()).error(R.drawable.app_icon_gray).into(cardObjectViewHolder.image);
}
}, new CardTappedListener() {
@Override
public void onCardTapped(CardObject cardObject) {
Intent intent = new Intent(context, TeacherProfile.class);
intent.putExtra("card_id", cardObject.getId());
intent.putExtras(cardObject.getCardBundle());
context.startActivity(intent);
}
});

Well that’s the end

Wheeew…. thats was a lot of text and code for one article. With this approach I dont have a lot of Adapters in my android project. Also when I add some cool animation or effects. All my lists can have access to it. And if another implementation of a “listview” comes up, changing to that new one will be fairly easier since I will only have to change one class.

The CardObject approach also helps you think straightforward about items in a list. After all they are just items in a list. This design can be adapted to more complex situations.

Happy coding.

Thank you for reading

Please let me know what you think in the comments. All suggestions are welcome.