Simple, Functional Async RecyclerView Diffing
A List undergoing change from State A to B

TJ Dahunsi
Feb 26 2019 · 6 mins
Android’s DiffUtil
and its DiffResult
output have received lots of coverage, and all warn about how diff calculation is expensive and shouldn’t be done on the UI thread. Most examples go ahead to calculate the diff on the UI thread anyway, and for very good reason: The RecyclerView
is very tetchy about modifications to it’s state from a non UI thread.
The persnickety nature of the RecyclerView
has been detailed exhaustively by Jon Hancock here. I highly recommend reading that article as it’s where I found my feet. Further reading should include Erik Hellman’s “A nice combination of RxJava and DiffUtil”. With the information in those articles digested, we can start to think of a generic one shot approach to tackling diffing in RecyclerView
s regardless of List
or RecyclerView
Adapter
type.
Let’s start with some code. The star of the show is a simple interface, Differentiable
:
public interface Differentiable { String getId(); default boolean areContentsTheSame(Differentiable other) { return getId().equals(other.getId()); } default Object getChangePayload(Differentiable other) { return null; } }
Differentiable Interface
That is, in order to be able to be recognized as distinct, an item need only provide a unique String
identifier. All other callbacks are optional, lending to a semi functional interface. Coverage on them can be seen in the Android docs of [DiffUtil.Callback
](https://developer.android.com/reference/android/support/v7/util/DiffUtil.Callback.html).
Next, let’s look at what the DiffCallback
for Differentiable
items would look like:
class DiffCallback<T> extends DiffUtil.Callback { private final List<T> stale; private final List<T> updated; private final Function<T, Differentiable> diffFunction; DiffCallback(List<T> stale, List<T> updated, Function<T, Differentiable> diffFunction) { this.stale = stale; this.updated = updated; this.diffFunction = diffFunction; } @Override public int getOldListSize() {return stale.size();} @Override public int getNewListSize() {return updated.size();} @Override public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { return apply(oldItemPosition, newItemPosition, (stale, current) -> stale.getId().equals(current.getId())); } @Override public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { return apply(oldItemPosition, newItemPosition, Differentiable::areContentsTheSame); } @Nullable @Override public Object getChangePayload(int oldItemPosition, int newItemPosition) { return apply(oldItemPosition, newItemPosition, Differentiable::getChangePayload); } private <S> S apply(int oldPosition, int newPosition, BiFunction<Differentiable, Differentiable, S> function) { return function.apply(diffFunction.apply(stale.get(oldPosition)), diffFunction.apply(updated.get(newPosition))); } }
A generic DiffUtilCallback that can diff any List
Finally, utilization of the above 2 to get a DiffResult
:
/** * Calculates the {@link androidx.recyclerview.widget.DiffUtil.DiffResult} between the source * list and the output of the combiner function applied to the source list and the additions. * </p> * Regardless of implementation, the combiner function is pure, it does not mutate the source * list or the additions list, and as such is best used for diff calculations * in a background thread. * * @param src The original source list in the {@link androidx.recyclerview.widget.RecyclerView} * @param additions Input to the combiner function to mutate the source list * @param combiner A function that yields a new list for which the diff will be calculated * @param diffingFunction A function that determines how items in the lists are differentiated * from each other * @param <T> Type parameter of the lists * @return A POJO containing the result of the diff, along with the output of the combiner * function */ @NonNull public static <T> Diff<T> calculate( List<T> src, List<T> additions, BiFunction<List<T>, List<T>, List<T>> combiner, Function<T, Differentiable> diffingFunction) { List<T> updated = combiner.apply(new ArrayList<>(src), new ArrayList<>(additions)); DiffUtil.DiffResult result = calculateDiff(new DiffCallback<>(new ArrayList<>(src), updated, diffingFunction)); return new Diff<>(result, updated); }
A simple method for calculating the diff between any list, on any thread
Let’s talk about that last bit some more and how it works. The first 2 arguments are Lists
of any type T
. The first is the original List
reflected by the current state of the RecyclerView
Adapter
. The second List
could be any non-null List
:
It could be the new state the
RecyclerView
should reflect.It could be a
List
of items to add to the existing list, perhaps theRecyclerView
endless scrolls and there’s new data to add.It could be a
List
to filter the currentList
on.
… and so on. All that matters is that it’s a List
of the same type T
.
The third argument, the combiner, is the real implementation detail. It’s what combines the source List
, and the additions, subsequently returning a new List
that represents the new state of the RecyclerView
. The 3 lines of code in the method body show that regardless of the implementation of the combiner function, the entirety of the operation is pure; there are no side effects. The operands in the combiner function are copies of the source and addition Lists
. Moreover, the DiffCallback
makes yet another copy of the source so if the source were to change at some point later, the DiffResult
is a perfect snapshot of the two states the RecyclerView
has transitioned between should the result of the diff calculation be applied to the RecyclerView
.
The last argument, diffingFunction
, is a function that maps the type T
to a Differentiable
. As mentioned earlier, the easiest implementation is to return a unique String
id for each item. Provided the hashCode()
method is properly implemented for T
, using
item ->; (Differentiable) String.valueOf(item.hashCode())
would suffice.
The result of the method, a Diff<T>
, contains the DiffResult
, and the new List
that would be accurately reflected via the application of said DiffResult
to the RecyclerView
, i.e a non generic Pair
. At this point nothing has really happened. The RecyclerView
’s Adapter hasn’t been notified, neither has the original List
changed, and that’s perfect because this calculation can be run on a background thread with no side effects whatsoever. As said before, the implementation is a pure function.
public class Diff<T> { public final DiffUtil.DiffResult result; public final List<T> cumulative; private Diff(DiffUtil.DiffResult result, List<T> cumulative) { this.result = result; this.cumulative = cumulative; } }
The Diff class
So how would one use this then? Consider a list of dancing tiles that change every 2 seconds. Using RxJava:
private void dance() { disposables.add(Flowable.interval(2, TimeUnit.SECONDS, Schedulers.io()) .map(__ -> makeNewTiles()) .map(newTiles -> Diff.calculate(tiles, newTiles, (tilesCopy, newTilesCopy) -> newTilesCopy)) .observeOn(mainThread()) .subscribe(diff -> { tiles.clear(); tiles.addAll(diff.cumulative); processor.onNext(diff.result); }, processor::onError)); }
Diffing with dancing tiles
The makeNewTiles()
method is invoked every 2 seconds on a background thread, at which point a new tile Diff
is calculated. On the main thread, the List
backing a RecyclerView
is updated, and the DiffResult
is pushed to the View which will eventually call DiffResult[.dispatchUpdatesTo](https://developer.android.com/reference/android/support/v7/util/DiffUtil.DiffResult.html#dispatchUpdatesTo(android.support.v7.widget.RecyclerView.Adapter))(adapter)
. No IndexOutOfBoundsException
; inconsistency detected error to worry about.
Result of the dance method
For endless scrolling, the code looks like this:
public Single<DiffUtil.DiffResult> getMoreTiles() { return Single.fromCallable(() -> Diff.calculate(originalTiles, generateTiles(), (origialTilesCopy, generatedTilesCopy) -> { origialTilesCopy.addAll(generatedTilesCopy); return origialTilesCopy; })) .subscribeOn(io()) .observeOn(mainThread()) .map(diff -> { originalTiles.clear(); originalTiles.addAll(diff.cumulative); return diff.result; }); }
Single from callable waits till the Single
is subscribed to, before generating new tiles and calculating the diff. Since the subscription is on a background thread, the diffing will also be done there. The combiner function simply adds the new items generated to the copy of the old ones then returns them. Back on the UI thread, the original list swaps its contents out for the result of the combiner and posts the DiffResult
to the hosting View
.
Endless Scrolling with Diffing
If you read Jon’s post, both these examples follow the queueing approach, as all diffing requests are handled by RxJava which queues them.
Coroutines would work just as well however. Wrap Diff.calculate
in a suspending function, and apply the the result when you’re back on the main thread, async diffing made simple.
The one caveat is the space complexity of diffing. There are 2 copies made of the original List
, and 1 copy of the additions. A small caveat for simplicity IMO. More importantly, this code lends itself to a plethora of use cases. I’ve used it for diffing endless scrolling lists, lists representing scanning for Bluetooth devices, to real time search results. Checkout the sample app below for a bunch of examples; including both described above. The endless scrolling list particularly, has been scrolled to 10,000 items using this diffing implementation with no issues.
Note: The AndroidX libraries does provide the ListAdapter
class to handle diffing for you internally, however, that requires delegating asynchronicity to the View
layer which makes me feel a bit uneasy. I prefer to keep my View
classes as simple as possible. Let the Repository
and ViewModel
classes handle the concurrency in the data layer. The View
should just be notified when it ought to change.
That’s all there is to it, reusable code to diff any RecyclerView
Adapter
backed by a List
of any type T
. You don’t have to create a different DiffCallback
for a different adapter
. You don’t even need to make your model objects implement Differentiable
, heck you could provide a different diffFunction
to diff 2 separate instances of the same type of Adapter
. It’s a simple functional approach to diffing, and there’s a lot to like.