A nice combination of RxJava and DiffUtil
If you are using RecyclerView and have stayed fairly up-to-date with the API changes, you are probably aware of the DiffUtil class that was…
If you are using RecyclerView and have stayed fairly up-to-date with the API changes, you are probably aware of the DiffUtil class that was added a few versions back. This excellent utility makes it easy to generate a set of calls to notifyItemInserted(), notifyItemRemoved() etc. on the adapter by simply comparing the existing version of the data with the new version. All you need to do is to implement a callback that does the comparison of the elements from the existing and new list.
private static class MyDiffCallback extends DiffUtil.Callback {
private List<Thing> current;
private List<Thing> next;
public MyDiffCallback(List<Thing> current, List<Thing> next) {
this.current = current;
this.next = next;
}
@Override
public int getOldListSize() {
return current.size();
}
@Override
public int getNewListSize() {
return next.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
Thing currentItem = current.get(oldItemPosition);
Thing nextItem = next.get(newItemPosition);
return currentItem.getId() == nextItem.getId();
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
Thing currentItem = current.get(oldItemPosition);
Thing nextItem = next.get(newItemPosition);
return currentItem.equals(nextItem);
}
}
DiffResult diffResult = DiffUtil
.calculateDiff(new MyDiffCallback(current, next), true);
diffResult.dispatchUpdatesTo(adapter);
The code above show how to implement the callback, how to perform the calculations and how to dispatch the notify-calls to the RecyclerView adapter.
However, there is a challenge with using this. If your data is very large, or the comparison you do is very complicated, you should avoid calling the DiffUtil calculation on the main thread. Moving this to a background thread solves the problem and you can then send the result in a message to the main thread where it gets called on the adapter after you have set the new data.
Now this is where things gets tricky. Because the DiffUtil calculation needs both the existing and the new list of data, you will need to access the existing data on the background thread as well. If your adapter has the method setData(), it will effectively need a getData() as well. This means that data will be accessed from multiple threads, so you probably will need some for synchronization or a thread safe data structure. How about if we could avoid this?
Enter RxJava, our magical solution to almost everything. Let’s say that you have an Flowable<List<Thing>> listOfThings, which will emit the latest version of the data your RecyclerView will show. We can subscribe to this on an IO or computation scheduler to make sure that whatever work is done doesn’t block the main thread, and then observe the events on the main thread where we just pass the data to the adapter.
ThingRepository
.latestThings(2, TimeUnit.SECONDS)
.subscribeOn(computation())
.observeOn(mainThread())
.subscribe(things -> {
adapter.setThings(things);
adapter.notifyDataSetChanged();
});
The code above shows how to make sure the expensive emission of our new list of data is done on the computation thread and how we then move to the main thread in our subscription where we call notifyDataSetChanged() on our adapter. This works, but won’t look very good as everything gets redrawn for each new list (which is why you want to call the other notify methods).
In order to make use of DiffUtil, we need to call calculateDiff() method and pass the DiffResult together with the latest List<Thing> to our subscription. An easy way to do this is to use the Pair class, which is a simple but powerful implementation of a tuple in the support library. Thus, we would change our subscription to receive an event of type Pair<List<Thing>, DiffResult>.
.subscribe(listDiffResultPair -> {
List<Thing> nextThings = listDiffResultPair.first;
DiffUtil.DiffResult diffResult = listDiffResultPair.second;
adapter.setThings(nextThings);
diffResult.dispatchUpdatesTo(adapter);
});
.scan()
The callback passed to DiffUtil.calculateDiff() needs both the current and the new list in order to work. How can we make sure that for every new list we get from our source, we also want to get the previous event? This is one of these times where RxJava shows it true power. One of the more mysterious operators in RxJava is scan(). Scan will basically give you the next and previous event and pass it to your function where you return the next event that will be passed on. This method comes in three variants, and the one we want is the second version which also takes an initial value. Our initial value to scan() will be a Pair consisting of an empty list and null as the DiffResult value.
We now call calculateDiff() and give it the list in our Pair (which is the previous list) and the next list. We use the result to construct a new Pair with the next list and our new DiffResult. Note that we’re not using the DiffResult in the incoming Pair in this method.
We got one more thing to think about. If we would leave it like this, the first event would contain a Pair where the DiffResult is null (from the initial value), so we would have to make a null check in our subscription. However, since this event would also be the same empty list as we started with, we can simply skip it using the skip(1) operator. This will omit the first event, which we’re not interested in anyway.
List<Thing> emptyList = new ArrayList<>();
adapter.setThings(emptyList);
Pair<List<Thing>, DiffUtil.DiffResult> initialPair = Pair.create(emptyList, null);
ThingRepository
.latestThings(2, TimeUnit.SECONDS)
.scan(initialPair, (pair, next) -> {
MyDiffCallback callback = new MyDiffCallback(pair.first, next);
DiffUtil.DiffResult result = DiffUtil.calculateDiff(callback);
return Pair.create(next, result);
})
.skip(1)
That’s it! This lets use DiffUtil on a background thread, using RxJava and some smart operators. There is also no need to consider any synchronization or concurrency for the current data held by the adapter. The result in my sample app can be seen below.
What if things are coming too fast?
There is a final caveat with this approach. What if events gets emitted faster than we can consume them? What if the DiffUtil calculation takes a few seconds and we get new sets of data faster than that? Here you have to make some considerations regarding the back pressure of RxJava. With RxJava 2, you can use Flowable instead of Observable and optionally configure a back pressure strategy that works for your situation. In my case, I just buffer all events to make sure everything gets processed and displayed. If my events would turn up too fast, I might consider using something like debounce() to make sure I don’t update the adapter too often. All of this depends on your particular use case, but the RxJava operators and back pressure strategies will help you here.
I hope you found this useful and wish you good luck with your future RxJava and DiffUtil adventures! I made a small demo sample of this that you can find at https://github.com/ErikHellman/RxJavaAndDiffUtil. Feel free to comment or submit PRs if you think things could be improved!
Thanks to Anup Cowkur, Dario Mungoi, Garima Jain and Hugo Visser (special thanks to him for the tip of using scan() with an initial value) for helping me proof read this post!