Skip to content

Diffing

Eli Hart edited this page Apr 5, 2017 · 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.

To avoid the manual overhead and boilerplate of implementing equals and hashCode on all your models, Epoxy generates model subclasses to do it for you.

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 - We are using a custom diffing algorithm that we wrote in house. The Android Support Library class DiffUtil was released after we completed this work. We continue to use our original algorithm because in our tests it is roughly 35% faster than the DiffUtil. However, it makes some optimizations that use more memory than DiffUtil. We value the speed increase, but in the future may add the option to choose which algorithm is used.