RecyclerView 2020: a modern way of dealing with lists in Android using DataBinding — Part 1

RecyclerView is undoubtedly one of the most important and widely used UI widgets available in Android SDK nowadays, and it’s really hard to imagine a modern application with no lists at all. There are hundreds of articles about this component and there is a lot of great libraries simplifying its usage provided by the community. Nevertheless, I’d like to add my 5 cents here and discuss traditional approaches of working with lists, what problems they bring, and how they can potentially be solved by using Android Data Binding Library.

Let’s take a look at a canonical implementation of aRecyclerView.Adapter, shamelessly borrowed from the official docs:

Quite a lot of code for such a simple task as rendering a list of strings on screen, isn’t it? But what’s even more important, this adapter is tightly coupled to a single data type (String) and XML layout. Essentially, we often have a 1 : 1 mapping between the number of lists in the app and the number of RecyclerView.Adapter implementations: we have to create a new adapter (including RecyclerView.ViewHolder) pretty much every time we’re dealing with a new list. What’s even worse, a significant part of these implementations consists of identical boilerplate code which is only there to match the contract of the RecyclerView.Adapter abstract class. Pretty annoying I’d say. But let’s continue with this adapter anyway and try to link it to a hypothetical screen based on Fragment or Activity.

Luckily, the days when writing all the business logic right inside Activities and Fragments was a common practice are gone and a modern Android developer would extract it into a separate entity like Presenter or ViewModel:

View models & presenters do not know anything about RecyclerViews, hence they can’t update the UI directly. Instead, we need to write some extra code in the UI layer to complete the chain:

Quite a typical code and I bet most of us have already seen or even written something very similar many times before. What problems does it have? First of all, we have to create an adapter manually and store a reference to it so it can be updated if data set changes. Second, we have to set up observing of the data exposed by View Model and react to these changes by updating the underlying adapter’s data set. While both these issues do not bring any architectural or ideological issue, they still lead to writing extra boilerplate code in our UI layer, we have to manually link the two worlds — plain & natural data flowing in business logic layer (View Models, Repositories, etc.) and UI handled by Views, Fragments or Activities.

Well, we’ve found the following areas for improvement in the “classic” way of working with Lists & Adapters in Android:

  1. We have to create new & new adapters for almost every single individual RecyclerView in the project. These adapters can’t always be reused and contain a lot of boilerplate code.
  2. It would be a bad idea to store RecyclerView's adapters right inside view models for lots of reasons (separation of concerns, view models reusability) so we have to expose domain-level data from View Models / Presenters and manually convert it to its UI representation by using adapters. This leads to writing extra code in Fragments and Activities + UI layer becomes the one who performs actual mapping, we expose domain (POJOs, Data Classes, etc.) format to UI layer so it can do the conversion.

As some of you probably know, there is a solution to both of these problems and it’s called Android Data Binding framework. Let’s get started…!

To begin with, let’s create a new data class which will be the only format used by our future universal Adapter:

  • data will hold… your data. It can also be treated as a “view model of individual list item”. It can either be something as simple as a plain POJO or something more complex with actual business logic, our adapter will not care about that and it is up to you to decide depending on the use case.
  • layoutId — this is the XML file containing layout for rows of the list. This is where our adapter will try to find variableId to initialize it with the value of data. Essentially, this is a reference to a layout that will be used to render the data.
  • variableId contains the name of the variable which will be used in your XML layout inside <data></data> tag. We’ll discuss this in more detail below. Also, check out the Data Binding : Dynamic Variables docs for more details.

Now it’s time to implement the universal DataBinding adapter itself:

That’s basically it, the MVP version of our universal adapter is ready. Let’s quickly discuss what’s going on here.

  • getItemViewType: as you probably know, this callback is typically used to support multiple view types within single RecyclerView & adapter. We simply return layoutId from our RecyclerItem and this trivial action allows our adapter to automatically handle different view types. We just use the generated XML layout’s ID as a “view type” identifier.
  • onCreateViewHolder: most of the data binding magic happens here. We use DataBindingUtil to dynamically create a new ViewDataBinding based on XML layout ID. All data-binding enabled XMLs have a generated class derived from this base class. We don’t know the name of this class here but we know that it will be generated for the given layoutId and it will inherit ViewDataBinding.
  • onBindViewHolder: here we need to link our ViewDataBinding with viewModel. Every data binding class has theserVariable() method which accepts variable ID and data of Object type. As you might have guessed, these are our variableId and viewModel from the RecyclerItem.

As we mentioned earlier, we’d like to avoid writing any glue code in our Fragments and Activities so the last step is to create a BindingAdapter which can help us to do everything with just 1 line of code in Fragment or Activity XML:

Now it’s time to see how all of that works together in practice. Let’s say we need to implement a screen displaying a list of Users. Here’s how your POJO class may look like:

Now let’s create a layout which will be used for individual rows:

Nothing new here for those who are familiar with the Data Binding framework. Now back to our view model: we still need to fetch Users and then “put” them into a RecyclerView somehow.

As you see, now we have List<RecyclerItem> instead of something like List<User> and we use user.toRecyclerItem() method to convert our data classes into something which can be recognized by RecyclerViewRecyclerItem . We don’t want to spoil our POJOs with logic which belongs to another layer of abstraction (UI) so let’s instead create a private extension in the file holding the ViewModel:

That’s the only “glue” code we need to write in order to bridge the gap between ViewModel’s natural data format (list of Users) and RecyclerView. The last step we need to do is to render the List<RecyclerItem> in our UI. Thanks to DataBinding and the BindingAdapter we created earlier, all of that can be done in XML:

The final step would be to assign your actual ViewModel to the viewModel variable from above:

That’s it and that’s the only code you need to write in your Activities or Fragments when you use DataBinding and the “adapterless” approach we’re discussing in this article. Pretty nice, huh? :-)

Let’s revisit what we’ve done so far to check whether it is any better than the original version with “1 adapter for 1 list”.

  • We no longer need to create new adapters. Ever. With minor additions, thisRecyclerViewAdapter can be the only adapter in your project.
  • Our Fragments and Activities no longer need to worry about adapters and data observing. The only thing they need to do is to assign a viewModel to the corresponding XML layout and this looks & feels pretty natural. Everything else is handled in XML with just a couple lines of code.
  • We no longer need to expose domain data format from View Models to Activities and Fragments. Instead, you just need to provide a generic list of RecyclerItems which hides actual data format from the UI and RecyclerItem can be handled by the UI directly with no extra transformations (thanks to the DataBindingAdapter and RecyclerViewAdapterit uses under the hood).
  • You don’t need to spoil your POJOs or recycler item View Models by extending some base class, you don’t even need to implement any extra interface. The POJOs or view models you use for RecyclerView look like regular classes. You just need to write a mapper somewhere where it makes the most sense for you (static or extension function returning a RecyclerItem).
  • There is no need to extend the adapter every time when a new item type is added. You can easily support as many view types in a single list as you want, you just need to define mappings from your data to RecyclerItem:

Much better than the new MergeAdapter, isn’t it? :-)

What’s next? As I mentioned earlier, the current implementation is a bit simplified: it will work just fine for relatively static cases with rare updates of the content. But for more dynamic scenarios it would be great to also integrate DifUtiland move its calculations to a background thread. I’ll try to uncover this topic (along with some other improvements) in the next article.

Thanks for reading and please let me know what do you think about this approach ;-)

UPD: Part 2 of this series can be found here

Android enthusiast

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store