A list is a basic component of any UI. Without lists our life would be much more inconvenient, to say least. All modern (presumably, any) mobile platforms are based on different kinds of lists, whether they are horizontal, vertical, grid-like and so far.
Android’s response to a user need of a list is rather elegant, but it was not like this from the very beginning. For quite some time, we had a component called
ListView. It had quite several issues, about which you can read in this great article: . However, time passes, and one day Google came up with a new component:
RecyclerView (official documentation).
There is a decent amount of articles explaining benefits of usage of RecyclerView. However, the majority of them claim that “a RecyclerView adds complexity to your code”. I strongly consider this to be false! RecyclerView is rather easy, if you put some time into creation of flexible foundation for it.
Furthermore, I have not seen a single article, which will provide a neat and clean (or at least usable without disgrace) solution how to build adapters for a
RecyclerView. So, let me show you my way of doing it!
So what’s the plan?
RecyclerView is an amazing tool, but there is some preparation we need to do before behold its power!
It is a client’s responsibility to handle all data changes. We need to have a data source for an adapter (like a
List) and we need to notify adapter about all changes inside that source (new data being inserted and old data being removed).
There is a bad practice of calling
notifyDataSetChanged()method to handle all data changes. This is bad, really bad! Not only it creates huge overhead, it also disables all animations, which you may want to add later.
We might want to have different cards (for example, actual data and a advertisement) in the same list. This may sound complex, but
RecyclerView has some really great tools of handling this situations, which we are going to explore.
We need to know about events happening inside our lists. Clicks, or any general changes should be planned in advance and described by a contract. We are going to learn best practices for that as well.
So lets solve those issues.
To the code!
Main concept behind all the code we are going to write is to delegate all data-related work to a helper class, which should be capable of handling data changes (have methods to add, remove and replace data) and also invoke all needed methods of
First thing is first, lets think: we need a class, which extends
RecyclerView.Adapter and implements some low-level logic, but not final implementation; it has to have methods to
update data, which it stores, and upon each type of transaction, it should notify about those changes.
And example of such a class may be this:
It is decently long, but you do not really need to dive deep inside (unless you want to, which always is a good thing). It’s a copy-paste solution.
Main points are: it is an
abstract class, which takes data-type and custom view holder, which extends
RecylerView.ViewHolder and has all needed methods, to operate stored inside data.
This class won’t be changed for any project or task you may use it for, as it’s just a simple wrapper. However, it does quite a valuable job: incapsulates all logic of operating data and notifying
RecylerView about those changes. Sweet!
So how do I use it?
All right, we need to create an adapter, which will have two card types: regular item and one on sale.
Basically, one adapter may contain even different data types. Just wrap them in generic wrapper, like
MyAdapterItem<T>, however we won’t cover it in this tutorial, as this is too easy to implement to emphasise it more than I have already did.
Lets define data we want to have inside our adapter:
saleAmount (by which we are going to determine which card to show) and
price, coupled with one method
calculatePrice() to calculate price with a sale applied.
Full source code for overview:
And more detailed dive:
BaseViewHolderabstract class, which will define all methods, which our different (for each card type)
- Create individual
ViewHolderfor each card type
getItemViewType()method, to differentiate our
- Inflate correct
ViewHolderby correctly overriding
BaseViewHolder abstract class, to define behaviour expected from our
unbinder is a ButterKnife field, needed to bind views of our
data is an instance of our data, which we will pass to each
ViewHolder, which is global variable (if, for example, you want to access it outside
bindData() method, e.g.
Finally, abstract function
bindData is a place, where all magic (assignment data to views) happens. We specifically pass our data as a argument and marked
data variable inside
lateinit: you have an option to assign
bindData argument to it (if you need to), but otherwise it won’t waste any resources.
Lets create individual
ViewHolders for normal and item on sale. For this tutorial logic is rather simple and there is not that much of the difference between them. To make things somewhat interesting, we defined different adapter’s callback methods for different good types and added extra
The code is rather straightforward, thus there is not really much to talk about. Define our views via
ButterKnife, bind it inside
init and override
bindData to assign data values to those views. Clean and beautiful, me liky!
We need a way to determine which
ViewHolder to instantiate. The way to do it is by overriding
Type enum comes in handy here. We implement some logic, which actually determines based on what we return different types and… that’s it!
Simple and clean: determine card type and return dedicated
ViewHolder for it.
Do notice that else branch of
when. This code will never execute, actually, as we have an
enumof types, and if you have covered all possible values of it, this would never happen. However (comma) if you have forgotten some types (this may happen, obviously, especially if there is like 10 types of cards) this will indicate you the direct source of the problem (especially if you provide more meaningful message). Beautiful!
At this point we are set with our adapter!
Now, lets use it, in a
Fragment, for example:
dataSet = mutableListOf()may look ugly to you, but hey, just add a constructor which takes just the callback and your are good to go!
That was rather super easy on the client side, was not it! Indeed it was. And this is the whole beauty of programming to me: write a lot of stuff, but keep is under the hood; client receives only neat, simple API.
This is going to be it for today, I really do hope that this lesson is going to help some people out there, seeking for an adequate way to design their
Of course this solution is not perfect. In fact, in my recent projects I use Rx-based version of adapter of this kind; this is a matter of a different tutorial for sure. Feel free to post any comments and suggestions, I would love to hear from you.