I recently wanted to dive deeper into Rx. So I experimented with Rx and the RecyclerView Adapters, and the results were pretty interesting!

With Rx in mind, I set out to accomplish three things:

Create a RecyclerView adapter which should be generic — one adapter class to rule them all! It should return bindings in the form of Rx streams! There should also be an option for supporting multiple item types!

Now, you may be thinking: this isn’t really necessary. I mean why use Rx in the first place with RecyclerAdapters? And why exactly do you need bindings as Rx streams?

Well that’s true. Personally, I thought it’d be a good experiment to incorporate Rx into RecyclerView Adapters, instead of using simple callbacks or delegates. So it was sort of experimental.

So I wrote a library called RxRecyclerAdapter to get Rx to work with the Adapters. Let’s break down how it simplifies use of the recycler adapters.

RxDataSource simplifies the use of RxRecyclerAdapter

Let’s say you have a beautiful String array list that you want to display:

//Dummy DataSet

dataSet = new ArrayList<>();

dataSet.add("this");

dataSet.add("is");

dataSet.add("an");

dataSet.add("example");

dataSet.add("of rx!");

Here’s what you would do:

Enable data binding by adding this into build.gradle

dataBinding {

enabled = true

}

2. create the layout file for the item:

<?xml version="1.0" encoding="utf-8"?>

<layout xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:tools="http://schemas.android.com/tools">



<LinearLayout

android:layout_width="match_parent"

android:layout_height="match_parent"

android:orientation="vertical"

android:padding="@dimen/activity_horizontal_margin">



<TextView android:id="@+id/textViewItem"

android:layout_width="match_parent"

android:layout_height="wrap_content"

tools:text="Recycler Item"/>



</LinearLayout>

</layout>

3. Create an instance of RxDataSource telling it what the dataSet type is:

RxDataSource<String> rxDataSource = new RxDataSource<>(dataSet);

4. Compose and then cast-call bindRecyclerView (passing in the RecyclerView and layout) with LayoutBinding. Because of casting, viewHolder can infer the type of binding.

rxDataSource

.map(String::toLowerCase)

.repeat(10)

.<ItemLayoutBinding>bindRecyclerView(recyclerView,

R.layout.item_layout)

.subscribe(viewHolder -> {

ItemLayoutBinding b = viewHolder.getViewDataBinding();

b.textViewItem.setText(viewHolder.getItem());

});

The output will be…

Note that calling observeOn(AndroidSchedulers.mainThread()) would be unnecessary here, as you’re already on the mainThread. And when you call it, it causes a delay of about ~20–30 milliseconds in the stream, which would lower your frame rate.

Now for a bit more practical example.

Let’s say you want to dynamically update the dataSet. Let’s say you want to search the dataSet and filter out the results specific results. Here’s how that would be done:

RxTextView.afterTextChangeEvents(searchEditText).subscribe(event -> {

rxDataSource.updateDataSet(dataSet)

.filter(s -> s.contains(event.view().getText()))

.updateAdapter();

});

In combination with RxBindings (because RxBindings are awesome), I register for textChange events. And when the event occurs I update the DataSet with the base dataSet!

Now this is important because the RxDataSource changes its dataSet instance when I call methods like filter, map and so on. So filtering needs to be done on the original dataSet, not the changed one. And… bam!

I did come across some limitations — one being that you can’t change the type of dataSet after it has been bound with the data source. So functions like map and flatmap can’t return a different type of dataSet. But I have yet to run into a situation where I needed to be able to change the dataSet at runtime.

RxRecyclerAdapter simplifies the situations where you have multiple item types

Now let’s say you wanted multiple Item types in your RecyclerView, for example a header and an item type. Then you would:

Create List of ViewHolderInfo specifying all the layouts

List<ViewHolderInfo> vi = new ArrayList<>();

vi.add(new ViewHolderInfo(R.layout.item_layout, TYPE_ITEM));

vi.add(new ViewHolderInfo(R.layout.item_header_layout, TYPE_HEADER));

2. Create instance of RxDataSource like before:

RxDataSource<String> rxDataSource = new RxDataSource<>(dataSet);

3. Compose and call bindRecyclerView passing in the recyclerView, the viewHolderInfo list and implementation of getItemViewType:

rxDataSource

.bindRecyclerView(recyclerView, viewHolderInfoList,

new OnGetItemViewType() {

@Override public int getItemViewType(int position) {

if (position % 2 == 0) {

return TYPE_HEADER; //headers are even positions

}

return TYPE_ITEM;

}

}

).subscribe(vH -> {

//Check instance type and bind!

final ViewDataBinding b = vH.getViewDataBinding();

if (b instanceof ItemLayoutBinding) {

final ItemLayoutBinding iB = (ItemLayoutBinding) b;

iB.textViewItem.setText("ITEM: " + vH.getItem());

} else if (b instanceof ItemHeaderLayoutBinding) {

ItemHeaderLayoutBinding hB = (ItemHeaderLayoutBinding) b;

hB.textViewHeader.setText("HEADER: " + vH.getItem());

}

}); /* and like before, you can do this as well

rxDataSource.filter(s -> s.length() > 0)

.map(String::toUpperCase)

.updateAdapter();

*/

Now recyclerView would look something like: