Skip to content

Diffing

Eli Hart edited this page Aug 7, 2018 · 5 revisions

Epoxy is especially useful for screens that have many view types backed by a complex data structure. In these cases, data may be updated via network requests, asynchronous observables, user inputs, or other sources that would require you to update your models and notify the proper changes to the adapter.

Tracking all of these changes manually is difficult and adds significant overhead to do correctly. In these cases you can leverage Epoxy's automatic diffing to reduce the overhead, while also efficiently only updating the views that changed.

EpoxyController classes use diffing automatically, and it can be optionally enabled in EpoxyAdapter classes. See the documentation for those classes for details on how they handle diffing.

Tracking model state

Epoxy calls equals and hashCode on each model to determine the current state of that model and to determine when a model has changed. It is important for each model to correctly implement these methods in order for diffing to work.

Epoxy generates model subclasses for you to avoid the overhead of implementing equals and hashCode manually.

Asynchronous Support

EpoxyController supports running the diff algorithm in a background thread to increase performance. See this section for details

Best Practices

When using diffing there are a few performance pitfalls to be aware of.

First, diffing must process all models in your list, and so may affect performance for controllers with hundreds/thousands of models. The diffing algorithm performs in linear time for most cases, but still must process all models in your list. Item moves are slow however, and in the worse case of shuffling all the models in the list the performance is (n^2)/2.

Second, each diff must calculate the model's state with equals/hashCode in order to determine item changes. Avoid including unnecessary computation in these implementations which can significantly slow down the diff.

Third, beware of changing model state unintentionally, such as with click listeners. For example, it is common to set a click listener on a model, which would then be set on a view when bound. An easy mistake here is using anonymous inner classes as click listeners, which would affect the model state and require the view to be rebound when the model is updated or recreated. In this case you can use the DoNotHash option (@EpoxyAttribute(DoNotHash) to tell the generated model to exclude that field from the state calculation. Another common mistake is modifying model state during a model's bind call.

A note about the algorithm - The Android Support Library class DiffUtil is used for diffing in EpoxyController. For legacy reasons, the older EpoxyAdapter uses a custom solution for diffing.