Kotlin Recipes for Android (II): RecyclerView and DiffUtil

As you may know, the Support Library 24 included a new really handy class called DiffUtil, which will let you get rid of the boring and error prone of detecting which cells changed and update them

If you haven’t heard about it, you can take a look at this nice article from Nicola Despotoski, which explains quite easily how to deal with it.

As usual, Java language introduces a lot of boilerplate, and I decided to investigate what we can get when implementing this using Kotlin.

The example

For the example, I’m creating a small App (which you can download from Github) that randomly selects the set of items that will be next included in the RecyclerView from a list of 10.

diffutil-recyclerview

So, from one iteration to the next one, some of them will be repeated, some of them will have disappeared, and some other will enter as new.

If you know how RecyclerView works, you may know that to notify those changes to its adapter, you have these three methods:

  • notifyItemChanged
  • notifyItemInserted
  • notifyItemRemoved

And their corresponding Range variations.

The DiffUtil class will do all the calculations for us, and will call the required notify methods.

The raw implementation

For a first iteration, we could just get the items from a provider, and let the adapter notify the difference (not the best code ever, but you’ll understand soon why it’s done like that):

private fun fillAdapter() {
    val oldItems = adapter.items
    adapter.items = provider.generate()
    adapter.notifyChanges(oldItems, adapter.items)
}

Easy: I save the previous items, generate the new ones, and say the adapter to notifyChanges, which is a method that uses DiffUtil:

fun notifyChanges(oldList: List<Content>, newList: List<Content>) {

    val diff = DiffUtil.calculateDiff(object : DiffUtil.Callback() {

        override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
            return oldList[oldItemPosition].id == newList[newItemPosition].id
        }

        override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
            return oldList[oldItemPosition] == newList[newItemPosition]
        }

        override fun getOldListSize() = oldList.size

        override fun getNewListSize() = newList.size
    })

    diff.dispatchUpdatesTo(this)
}

This is really annoying, because most part of the code is basically boilerplate, but we’ll get back to it later.

Now, you saw that I’m calling notifyChanges just after setting the new set of items.

So why don’t we delegate that behaviour?

Using delegation to make notifications simpler

If we know that we are notifying every time the set of items change, let’s just use Delegates.observer, which leaves an awesome code:

The activity is much simpler:

private fun fillAdapter() {
    adapter.items = provider.generate()
}

And the observer looks quite nice:

class ContentAdapter() : RecyclerView.Adapter<ContentAdapter.ViewHolder>() {

    var items: List<Content> by Delegates.observable(emptyList()) { prop, oldList, newList ->
        notifyChanges(oldList, newList)
    }
    ...
}

Awesome! But this can be better.

Using extension functions to improve adapter capabilities

Most of the code in notifyChanges is boilerplate. If we use data clases, we just need to implement a way to detect that two items are the same, even when their content isn’t.

In the example, the way to identify this is the id.

So I’ll create an extension function for the adapter that will do most of the hard work for us:

fun <T> RecyclerView.Adapter<*>.autoNotify(oldList: List<T>, newList: List<T>, compare: (T, T) -> Boolean) {
    val diff = DiffUtil.calculateDiff(object : DiffUtil.Callback() {

        override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
            return compare(old[oldItemPosition], newList[newItemPosition])
        }

        override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
            return oldList[oldItemPosition] == newList[newItemPosition]
        }

        override fun getOldListSize() = oldList.size

        override fun getNewListSize() = newList.size
    })

    diff.dispatchUpdatesTo(this)
}

The function receives the two sets of items, and another function. This last parameter will be used in areItemsTheSame to decide whether two items are the same or not.

And now to the call:

var items: List<Content> by Delegates.observable(emptyList()) { prop, oldList, newList ->
    autoNotify(oldList, newList) { o, n -> o.id == n.id }
}

Using composition

I can understand that you don’t like the previous solution very much. It’s rather specific to a particular case, and you don’t want that all adapters can use it.

But there’s an alternative: an interface.

Sadly, interfaces can’t extend classes as they did at some point of Kotlin preview (I really hope it’s re-added in the future). This let you use the methods of the extended class, and forced classes that implement the interface to be of that type.

But we can get a similar result by moving the extension function into an interface:

interface AutoUpdatableAdapter {

    fun <T> RecyclerView.Adapter<*>.autoNotify(oldList: List<T>, newList: List<T>, compare: (T, T) -> Boolean) {
        ...
    }
}

And the adapter just needs to implement this interface:

class ContentAdapter() : RecyclerView.Adapter<ContentAdapter.ViewHolder>(), AutoUpdatableAdapter {
    ....
}

And that’s all! The rest of the code remains unchanged.

Conclusion

There are several ways to use DiffUtils that make code look great and simpler than in Java. You can choose any of them.

If you want to try any particular solution, go to the repository and move to the specific commit.

14 thoughts on “Kotlin Recipes for Android (II): RecyclerView and DiffUtil”

  1. Thank you Antonio, please can you share an example with Realm.
    I try to change var items from List to RealmResult but I don’t know what pass to “by Delegates.observable(emptyList())”

  2. Thanks Antonio, do you think that is better kotlin for andriod, for a junior developer, or for the moment continue with Java?

    1. No, I’d learn Java first because most companies are still using Java, and because most of the literature is in Java too, so it’ll be easier for you to find solutions to your questions. Once you have some knowledge about the Android framework, feel free to learn Kotlin.

    1. “new” is not a reserved keyword in Kotlin, so you can use it freely. But yeah! Feel free to use the variable name you prefer.

  3. I believe the use of “new” as variable name negatively affects the readability of the code used in this article because it highlighted as a keyword. Many thanks anyway.

  4. Great article Antonio!
    But I have an issue, this is just refreshing my items the first time I set the adapter list but later when I re set that list nothing is refreshing I don’t know why.

    Any idea of what’s going on?

    1. Thanks! Did you try the example in the repo? Maybe you can compare and extract conclusions. No idea what could be TBH…

    2. Dominik Engelmann

      Same with me here. I have to explicitly set
      adapter.notifyDataSetChanged() to get the new items shown

Comments are closed.