The Evolution of RecyclerView Adapter in OpenSooq

Our Android app has many major components can help our business to grow, we have to provide our user with a seamless experience to sell and buy fast. However, we are building listing items with action for our users. The complexity of the listing items comes from handling and tackling RecyclerView.

RecyclerView by far is the biggest layout in Android, and that’s because a Recyclerview is a viewport into a huge virtual layout, and we are facing the challenge of redundant adapters and the reusability of that component. So, this article demonstrates how we started writing adapters and the story of building listing items until these days.

LinearLayout, RelativeLayout, and other ViewGroup  can only contain content as much as the viewport but Recyclerview can contain an infinite amount of content  (if you keep loading content) in an efficient way, using the concept of view recycling which is like another lifecycle inside the activity lifecycle your items live and die and they live again!
And one of the most important Recyclerview components is the Adapter and implementing adapters is one of the most frequent tasks for an Android developer. It’s the base for every list. Looking at apps, lists are the base of most apps. I will talk about the evolution of writing adapters

We started OpenSooq with a simple list with one item.

level 0

so simple task

public class PostsAdapter extends RecyclerView.Adapter<PostsAdapter.MyViewHolder> {
 
    private List<Posts> postslist;
 
    public class MyViewHolder extends RecyclerView.ViewHolder {
        
        public MyViewHolder(View view) {
         ....
        }
    }
 
 
    public MoviesAdapter(List<Posts> list) {
        this.postslist = list;
    }
 
    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.post_item, parent, false);
 
        return new MyViewHolder(itemView);
    }
 
    @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
       ...
    }
 
    @Override
    public int getItemCount() {
        return postslist.size();
    }
}

but we already know that The world ain’t all sunshine and rainbows This is the fact!
the longer you work on an app you probably know the view types just sort of multiplies like bunnies and they are a huge pain point.
now let me show how we started doing it and what we did wrong. So after that, the product manager wanted to add a header.


let’s do multiple items.

level 1


    private static final int TYPE_HEADER = 0;
    private static final int TYPE_ITEM = 1;
   
 @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == TYPE_ITEM) {
            return new VHPosts(null);
        } else if (viewType == TYPE_HEADER) {
            return new VHHeader(null);
        }
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof VHItem) {
            String dataItem = getItem(position);
            //cast holder to VHItem and set data
        } else if (holder instanceof VHHeader) {
            //cast holder to VHHeader and set data for header.
        }
    }

    @Override
    public int getItemCount() {
        return data.size() + 1; // + 1 for the header :)
    }

    @Override
    public int getItemViewType(int position) {
        if ( position == 0)
            return TYPE_HEADER;
        return TYPE_ITEM;}

    private String getItem(int position) {
        return data.get(position - 1);// - 1 for the header 
    }

 

We can say this will work but its a bit ugly right, as long as you have the only header, just two views you are ok, but as I told you before multiple types are like bunnies they don’t get enough :).
so the product manager wanted to add a footer, you guessed it
we will add a new type and your adapter will look like this

onCreateViewHolder(ViewGroup viewGroup, int viewType) {

    switch (viewType) {
        case TYPE_HEADER:
            View header = mInflater.inflate(R.layout.header, viewGroup, false);
            return new HeaderHolder(bannerRow);

        case TYPE_ITEM:
            View normalpost = mInflater.inflate(R.layout.row_new_post, viewGroup, false);
            return new PostViewHolder(normalpost);

        case TYPE_FOOTER:
            View feedbackView = mInflater.inflate(R.layout.footer, viewGroup, false);
            return new FooterHolder(feedbackView);
    }
public int getItemViewType(int position) {
        if ( position == 0)
            return TYPE_HEADER;      
   else if (position == getItemCount() -1)
                return TYPE_FOOTER
           return TYPE_ITEM;
}

and sure don’t forget the override this method

public int getItemCount() {
        return data.size() + 2; // + 2 for the header and footer :)
    }

hang on we are still in begging, so we were ok with this spaghetti code, A few weeks later product manager came to tell us we will show banner ads  The advertisement company could display banners in OpenSooq android app. and yes the advertisement banner should be displayed along with other items in a RecyclerView

since the position of the ad to be showing between other items is dynamic our way above won’t work
so we came up with a new solution.

level 2

the solution was to add new attributes the Post item like isHeader, isFooter , isBannerAd etc..
and while fetching the data we will those items into our adapter
the code will be like this

private void addPosts(ArrayList<Post> postsFromApi ){
    ArrayList<Post> finalPosts  = new ArrayList<Post>();
    Post header = new Post();
    header.setIsHeader(true);
    finalPosts.add(header);
    finalPosts.add(postsFromApi);
    addAdsBanner(finalPosts);
    Post footer = new Post();
    header.setisFooter(true);
    finalPosts.add(footer);
    adapter = new PostsAdapter(finalPosts)
}

and inside your adapter

@Override
public int getItemViewType(int position) {


    if (this.postsList.get(position).isHeader())
        return HEADER_TYPE;

    else if (this.postsList.get(position).isBanner())
        return BANNER_TYPE;

    ...
}

what do you think?

 

NO

now let me tell  why this is not a good idea

1-first from an OOP concept it’s totally wrong, the header is not a post the banner is not a post!!

2- this will work until you need a data from the banner like name, text,color.. , you will not add these to the post object would you?

level 3

so we were ok with the solution above until the product manager wanted a new item to the Rcyclerview but the problem is the new item will contain attributes we need to put them in some object and for sure it’s not the Post!
the solution:
Since we have different types of items we want to put into the same List, we’ll make an interface for that.
and your code will be like this

public interface ListItem {
    int TYPE_POST = 1;
    int TYPE_HEADER = 2;
    int TYPE_BANNER = 3;
    int TYPE_NEW_ITEM = 4;
 int getListItemType();
}

and inside each item, we implement this

public class TypeA implements ListItem {
    private String text;

    public TypeA(String text) { this.text = text; }

    public String getText() { return text; }

    public void setText(String text) { this.text = text; }

    @Override
    public int getListItemType() {
        return ListItem.TYPE_A;
    }
}

And your adapter list will be a list of ListItem.

that seems working and solve all the problems that we were facing but one problem here the Boilerplate!!.
You didn’t get me? take a look, how the code will look like

 

so ugly right? This clearly violates the SRP (Single Responsibility Principle) that states one class should be kept focused on a single concern. In the code above, the adapter deals with too many responsibilities: creating ViewHolder determining which ViewHolder to use, binding items to ViewHolder etc.

level 4

so our problem now that the code is not clean, let’s start cleaning step by step starting from cleaning the onBindViewHolder
we can solve this problem by creating the bind method inside the viewholder and to get rid of the instance of checks we will create a baseview holder so your code will look like this

 

public abstract class AbstractViewHolder<T> extends RecyclerView.ViewHolder {
    AbstractViewHolder(View view) {
        super(view);
    }

    public abstract void bind(T element);
}

so your onBindViewHolder had some makeup on and looks so beautiful.

@Override
public void onBindViewHolder(AbstractViewHolder holder, int position) {
    holder.bind(elements.get(position));
}

so now the onCreateViewHolder turn, let a look how it working it took a type then it returns a new view holder depending on the type sounds familiar ha? factory pattern  🙂
so we will create a factory method for that to separate the logic from the adapter

public  class typeFactory {
    @Override
    @SuppressLint("DefaultLocale")
    public AbstractViewHolder createViewHolder(View parent, int type) {
        AbstractViewHolder createdViewHolder;
        switch (type) {
            case ListItem.POST_ITEM:
                createdViewHolder = new PostHolder(parent);
                break;
             ....
       return createdViewHolder;
    }
}

and your method will look like :

@Override
public AbstractViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    Context context = parent.getContext();
    View contactView = LayoutInflater.from(context).inflate(viewType, parent, false);
    return typeFactory.createViewHolder(contactView, viewType);
}
also you can get ride of this numbers
public interface MyAdsListItem {
    int TYPE_POST = 0;
    int TYPE_SPOTLIGHT = 7;
    int TYPE_FOOTER = 8;
    int TYPE_LOOD_MORE = 9;

    int getMyAdsItemType();
}
and replace it withlayout ids. With this trick you don’t need the artificial type mappings by simply using the layout id you’re inflating.

now your adapter is clean and perfect 🙂

Conclusion

RecyclerView is a very powerful widget, and understanding how we can use it can save us a lot of time. I know that getting your hands on new things can be challenging for someone new, and that’s why we see the importance of this article.

We are open for your suggestions!