RecyclerView Adapters
Intro
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!
Issue #1
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.
Issue #2
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.
Issue #3
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 RecyclerView.Adapter
class.
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 add
, remove
and 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:
Just a name
, 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:
- Create
BaseViewHolder
abstract class, which will define all methods, which our different (for each card type)ViewHolders
should implement - Create individual
ViewHolder
for each card type - Override
getItemViewType()
method, to differentiate ourViewHolders
. - Inflate correct
ViewHolder
by correctly overridingonCreateViewHolder()
method.
#1
Lets create BaseViewHolder
abstract class, to define behaviour expected from our ViewHolder
s:
unbinder
is a ButterKnife field, needed to bind views of our ViewHolder
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. ButterKnfie
@OnClick()
method.
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 ViewHodler
as lateinit
: you have an option to assign bindData
argument to it (if you need to), but otherwise it won’t waste any resources.
#2
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 saleAmount
to SaleGoodHolder
.
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!
#3
We need a way to determine which ViewHolder
to instantiate. The way to do it is by overriding getItemViewType()
method:
Our Type
enum comes in handy here. We implement some logic, which actually determines based on what we return different types and… that’s it!
#4
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 anenum
of 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!
Usage
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 RecyclerView
adapters.
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.