This is the second post in a series that looks into calculating diffs between two lists on Android. You can read Part 1 here. In this post, we will look at how other platforms handle list diffing.
Edit: Edited to add code snippets for each platform.
Swift Standard Library
It looks like this facility is intended as a general purpose list diff API, not specific to UI programming. Remember, it is in the standard library so it can be used in backend server programming, for example.
Some notable features of this API
- The most interesting feature is the return type:
CollectionDifference. This API provides you a way to iterate through all the diff operations, or even to only pick the insertions (or removals). This is different from how Android does it. More on this in a minute.
- By default, it does not detect moves, but there's an
CollectionDifferencethat you can use if you want to do this
- It uses equality by default for the comparison, but you can customize how the comparison occurs using
difference(from: by:)variant. Here you pass in a closure that returns a
Boolso you can use whatever logic you wish to compare the elements.
CollectionDifference provides in itself a Collection of
CollectionDifference.Change - which is an enum with 2 values:
.insertprovides you with an
elementand its offset in the final list
.removeprovides you with an
elementand its offset in the original list
associatedWithparameter of the enums inform you about moves
There's no concept of "changes" in this API - i.e., it does not tell you if an item retained its identity but did not retain equality.
I could not find a way to convert positions between old and new lists, but I'm not sure if it is ever required when using this API in practice.
Swift Apple platforms
We started this series with an example of how Android's RecyclerView animates between 2 lists using DiffUtil. It should come as no surprise that Apple's UI frameworks have similar capabilities too.
It has always been possible to achieve this on Apple platforms but it has been verbose and error-prone (frequently giving rise to the Swift equivalent of
ArrayIndexOutOfBoundsException). Recent API improvements have greatly enhanced the developer ergonomics here.
The headline API is
UITableViewDiffableDataSource and friends (quite a mouthful!). This is completely out of my comfort zone so I'll point you to these talks from WWDC (there are also PDF slides available) if you want to learn more. I will point out though, that the items participating in this API need to be
Hashable. This is how the framework decides that items have "changed". It fulfils the role of
areContentsTheSame() from Android's DiffUtil.
I could not find any official API for List Diffs in Flutter. However, there's a third party library that is inspired by Android's DiffUtils. It is called AnimatedStreamList. The relevant files in this repo are
Of note in this library:
- It returns a
List<Diff>. In this sense it is similar to the Swift implementation
Diffcan be one of
ChangeDiff. This library does not implement moves.
- Each instance of
size. In this respect, it is similar to the Android DiffUtil implementation.
- It uses an
Equalizerto customize the comparison.
Angular has an
IterableDiffer API that can be used to compute the diff between 2 Iterables. From what I can tell, it is not intended to be used directly by applications, instead it is used internally by the framework (for example, by the
NgForOf directive). This article goes into the nuts and bolts of this API.
The interesting classes are
IterableDiffer: The entry point of the API. Offers the
IterableChanges: The diff result, which in itself is an Iterable
IterableChangeRecord: Each individual update operation
IterableChanges interface is pretty interesting: it exposes functions to iterate over the changes in a variety of ways (all updates, only additions, only removals etc). The
DefaultIterableDiffer accepts a
TrackByFn argument, which fulfils the role of Android's
IterableChangeRecord is also interesting: It does not directly state the diff operation. Instead, it contains
previousIndex. Together, these can be used to decide if an item was added, removed etc. It also fulfils the role of position conversion APIs in Android.
In practice, you'd probably use the
IterableChanges API to figure out the additions and removals.
At a glance
Here's a table summarizing all the diff APIs across these platforms.
|Android||Swift||Flutter (3rd party)||Angular|
|Position conversion||Methods on
A note about declarative UI frameworks
This series of blog posts actually started when I was trying to implement custom animations for a list view on Android. When I started this research, the question I wanted to answer was
How do declarative UI frameworks deal with allowing custom animations for changes in lists?
Note that declarative UI frameworks in general receive a UI state and render that state. They don't have a concept of "previous state" so "this item was removed" animation does not fit into this paradigm.
So far, I haven't found an answer to this question!
- SwiftUI provides some default animations, but I did not find a way to customize them.
- Flutter has no official APIs for this use case.
- Angular has some APIs that look like they are used internally. I'm way out of my depth about Angular to form any practical opinion about it.
It will be really interesting to see how Jetpack Compose is going to solve this problem!
After my research for this post, I came to the conclusion that Android's DiffUtil API is the most flexible of all. It is the lowest level API for exposing the diff operations (the
ListUpdateCallback). All other platforms expose collection-style APIs for this purpose.
I reckon Android has this low-level API because it plays well together with RecyclerView Adapter API. One can write a wrapper to expose it as a collection-style API.
That is exactly what we will do in the next post in this series: Look at an example situation where RecyclerView might not be best fit, and instead wrap the
ListUpdateCallback to implement some custom UI.