DiffUtil in Xamarin.Android

Viewed 506

Junior developer here so please play nice :)

My app uses a RecyclerView to display a list of items returned from a server. The adapter and refreshing works fine however, the app hangs/freezes temporarily when updating/refreshing the list.

I'm confident that it's freezing when it hits NotifyDataSetChanged() since this redraws everything in the list (there can be hundreds of items in the list). After looking online, it appears that DiffUtil may be exactly what I'm after but I can't find any documentation or tutorials for Xamarin.Android, just regular Java based Android and I don't understand either language enough to translate it.

If anybody can point me in the right direction it would be much appreciated!

2 Answers

I was able to get DiffUtil working in Xamarin.Android after reading this article from VideoLAN: https://geoffreymetais.github.io/code/diffutil/. He explains it very well and the examples in his project are very useful.

Below is a "universal" version of my implementation. I would recommend reading up on what each of the override calls does before implementing your own callback (refer to link above). Believe me, it helps!

The callback:

using Android.Support.V7.Util;
using Newtonsoft.Json;
using System.Collections.Generic;

class YourCallback : DiffUtil.Callback
{
    private List<YourItem> oldList;
    private List<YourItem> newList;

    public YourCallback(List<YourItem> oldList, List<YourItem> newList)
    {
        this.oldList = oldList;
        this.newList = newList;
    }

    public override int OldListSize => oldList.Count;

    public override int NewListSize => newList.Count;

    public override bool AreItemsTheSame(int oldItemPosition, int newItemPosition)
    {
        return oldList[oldItemPosition].Id == newList[newItemPosition].Id;
    }

    public override bool AreContentsTheSame(int oldItemPosition, int newItemPosition)
    {
        // Using JsonConvert is an easy way to compare the full contents of a data model however, you can check individual components as well
        return JsonConvert.SerializeObject(oldList[oldItemPosition]).Equals(JsonConvert.SerializeObject(newList[newItemPosition]));
    }
}

Instead of calling NotifyDataSetChanged() do the following:

private List<YourItem> items = new List<YourItem>();

private void AddItems()
{
    // Instead of adding new items straight to the main list, create a second list
    List<YourItem> newItems = new List<YourItem>();
    newItems.AddRange(items);
    newItems.Add(newItem);

    // Set detectMoves to true for smoother animations
    DiffUtil.DiffResult result = DiffUtil.CalculateDiff(new YourCallback(items, newItems), true);

    // Overwrite the old data
    items.Clear();
    items.AddRange(newItems);

    // Despatch the updates to your RecyclerAdapter
    result.DispatchUpdatesTo(yourRecyclerAdapter);
}

It is possible to optimise it even more by using custom payloads etc. but this is already head and shoulders above calling NotifyDataSetChanged() on your adapter.

Last few things that I spent a while trying to find online:

  • DiffUtil does work in fragments
  • DiffUtil can update an empty list (i.e. there doesn't need to be pre-existing data)
  • The animations are handled by the system (i.e. you don't have to add them yourself)
  • The method that calls DispatchUpdatesTo(yourRecyclerAdapter) does not have to be within your adapter, it can be within your activity or fragment

This is quite new for me too, and I have seen this before. I tried it literally just now, and got it working after a half an hour.

So some of it comes from here: https://medium.com/@iammert/using-diffutil-in-android-recyclerview-bdca8e4fbb00

And basically what it says is to:

  1. Have 2 different points of your data structure (List, IEnumerable, etc...) it sounds like you already have that, so that's good.

  2. Have a DiffUtil.Callback class where you'll be passing in the old and new data that this class will compare one against the other.

  3. Have a method that will dispatch the updates along with your utility class. Though how the post has it is a bit wrong since he didn't update the old data. But if you did that, then it'll have to work as it does for me.

Let me know if you have questions or run into issues.

Related