The Diffing Dilemma! All about diffing, from RecyclerView to LazyLists — An Introduction

the-diffing-dilemma!-all-about-diffing,-from-recyclerview-to-lazylists — an-introduction
Photo by Dewang Gupta on Unsplash

The Diffing Dilemma! All about diffing, from RecyclerView to LazyLists — An Introduction

This is an introductory article to the multipart series on “All about diffing, from RecyclerView to LazyLists”. You can find the next part here.

Sometime around the end of the year in 2020, I started exploring Jetpack Compose since we wanted to introduce it to our codebase. The thing that I was most curious about was how it works with MVI. I explored it and as it turns out, Jetpack Compose was the missing piece to the MVI puzzle, i.e. Compose is just the right tool that was missing to implement proper diffing in views. More about it in this article. Little did I know that there was more to this puzzle than just simple views. Well, all good with views and diffing, but, what about lists and grids? Does Jetpack Compose work well when our UI state contains a list of items? RecyclerView, being such a mature tool is so powerful that anybody would think twice before migrating from their stable (pun-intended) implementations to the lazy lists of Jetpack Compose.

This is when it all started, the explorations and the search for all the answers before we adopt Compose for our Home! screen. A high-risk screen that shows up right in our users’ face each time they open the app. It was only last year that we had refactored our Home Screen such that it supports multiple view types quite easily with proper diffing provided via the AsyncListDiffer and it was pretty stable now. We were now re-writing our Home Screen to make it slightly server-driven and also re-arranging the layout of the Home Screen. With RecyclerView, we could do it easily since it supports multiple view types, of course with some (**ahem** a lot **ahem**) of boilerplate. All the conditions were favourable for us to move to Compose lists and just rewrite the whole thing in compose. It was the perfect contender for rewriting in Compose, but is it?

Immutable UI state

Since our UI state is immutable, each action emits a new state, i.e. in case of lists, a different instance of list items but essentially containing same ids, RecylerView handles it efficiently using the ListAdapter, but would Compose be just as efficient? How would we define areItemsTheSame() and areContentsTheSame() in compose? Would Compose also be able to “recycle” or in compose terms, even reuse the compositions in comparison to the good old RecyclerView?

Well, the answer is “Ummmm yeah, sure”.

Before we answer all the above questions in detail, I would like to explain the problem itself a little bit more and briefly cover how our lovely, hulk of a tool, i.e. RecyclerView does this so well. I might sound a little biased towards RecyclerView so far but rest assured I won’t shy away from explaining its drawbacks as well.

Back to the basics.

What is Diffing?

“Diffing is the process of comparing two sets of data to highlight their differences and calculating the set of changes required to create one set of data from the other”

Diffing is everywhere, our version control systems like git perform diffing to highlight the changes between two sets of files. Web also uses diffing to compare virtual DOMs. SwiftUI uses diffing to efficiently update its views.

What da Diff?

In order to understand why we need diffing let’s have a look at this GIF below:

User interacts with the view, View emits an event to our favorite MV* architecture, which in turn emits a state, state populates the view which is shown to the user
Illustration 1: Example state flow
  • User interacts with the view
  • View emits an event to our architecture
  • A new state is emitted which populates the view
  • View is shown to the user

Now subsequently, what happens when another event is emitted?

Illustration 2: Example state flow, view is redrawn
  • User again interacts with the view
  • View emits another event to the model
  • Another state is emitted
  • Now the view has two options: it can either redraw itself as in the Illustration 2 above

Or

Illustration 3: View performs efficient diffing instead of redrawing everything

View can efficiently perform diffing against the previous state and only emit the least amount of changes to update itself as shown in Illustration 3.

Diffing in Android View System

It is quite hard to achieve proper diffing in the Android View System and this responsibility mostly lies with us. In Android View System, implementing diffing means that we compare the previous state value against the new one before redrawing every single component. Now, the component could be as simple as a TextView but it could also be something as complex as a canvas editor screen with tons of tools like filters, color picker, font editor, layout picker, etc, to choose from which have sub-tools of their own. Implementing proper diffing within complex views means, depending on the change in state we add/remove views manually, keeping previous state and update only the changed portion and trust me it is a nightmare to do it properly.

Compose, our modern UI toolkit has done most of the heavy lifting for us. Compose internally uses a diffing mechanism like react’s virtual DOM to efficiently update, only the parts of our complex screens that have changed.

What about Lists?

Well, as we saw previously, it is a bit complex to handle diffing properly within the Android View System, but lists are an exception. Android View System has quite a mature tool, i.e. RecyclerView which comes with an optional API called ListAdapterto handle diffing.

ListApater uses AsyncListDiffer(DiffUtil) to calculate the minimal set of updates to transition from the old list to the new one. As the name suggests this is offloaded to a background thread and then dispatched back to the RecyclerView. DiffUtil uses Eugene Meyer’s Diffing algorithm to calculate an EditScript which is then converted into a list of minimal notifyX() operations to efficiently transition from the previous list state to the new one. All we need to do is implement areItemsTheSame() and areContentsTheSame() callbacks and all the heavy lifting is done by RecyclerView itself.

What about Compose’s LazyList?

Well, it definitely deserves a blog of its own. Let’s look at it separately in this blog.


The Diffing Dilemma! All about diffing, from RecyclerView to LazyLists — An Introduction was originally published in Google Developer Experts on Medium, where people are continuing the conversation by highlighting and responding to this story.

Total
0
Shares
Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Post
19-amazing-examples-of-the-best-brands-on-social-media

19 Amazing Examples of the Best Brands on Social Media

Next Post
-pure-css!-neural-network-/-ai…it’s-easier-that-you-think!-

😱 Pure CSS! Neural Network / AI…it’s easier that you think! 🤯

Related Posts