Taming partial Epoxy models update

Alla Dubovska
xorum.io
Published in
3 min readFeb 24, 2020

I have been using Epoxy for more than a year. It’s an open-source Android library for building complex screens in a RecyclerView developed by Airbnb. The main advantage of it is automatic intelligent diffing of item changes, so UI is updated smoothly and with no developer efforts.

Sounds cool! But I spent whole evening trying to update part of the cell without animated reloading of all items in the list. At the end it was very straightforward, so let me share my solution with you.

Desired result

Sample project

Let’s consider an application for learning French for English-speaking users. We’ll have some simple phrases/words with English and French version that are displayed in a list. At the beginning French translations are hidden, but user can reveal all of them by pressing a button.

This sample project can be found on GitHub. Commits are self-explanatory and split by features. These are starter commits, which aren’t complicated, so we won’t waste time on them:

  1. Create a simple project with MainActivity : b2ee0c1
  2. Add Epoxy dependency in build.gradle : 48a021c
  3. Add data class Phrase and MockDataSource that only returns the list of hardcoded phrases : 2cd2aef

Display phrases

In af12129 basic EpoxyController and EpoxyModel are added to MainActivity as inner classes. Along with adding EpoxyRecyclerView to XML and basic layout for a list item, this allows us to display phrases in UI.

Setup RecyclerView

This is important to mention that chosen approach for creating EpoxyModel is one of a few available, but I prefer this particular one for simplicity of use and end-to-end Kotlin experience.

Every EpoxyModel must have its unique id. In our case it’s generated like this: PhraseEpoxyModel — $phrase.

Hide / reveal translation

If you still remember our intention, we need to allow the user to reveal (and then hide if needed) English translation of French phrases / words. This can be done by introducing a variable isTranslationDisplayed into MyEpoxyController and requesting models rebuild on each variable’s change.

But it won’t work like this, because we don’t take into account isTranslationDisplayed in model’s id. That’s why we append it to each model’s id. Works like a charm, but… it’s blinking and scrolling to the top.

Blinking animation

Solution

It took me 4 hours to find the magic wandEpoxyModel has another bind method dedicated to Partial Model Update:

bind(view: View, previouslyBoundModel: EpoxyModel<*>)

Exactly what we need to get rid of UI artefacts.

Important note: your EpoxyModel must be a data class ( equals and hashcode are correctly overriden) and you don’t need add isTranslationDisplayed to model’s id, because it’s already added to model itself.

Why this works? Because, as official Epoxy documentation states:

When a model’s state changes, and the model is bound to a view on screen, Epoxy rebinds the view with only the properties that changed. This partial rebind is more efficient than rebinding the whole view.

Bonus

Let’s imagine that users want to be able to see the translation for specific phrase by tapping on it (true story 😉). It’s super easy with the latest implementation, see details in 8e2bfbd.

--

--

Alla Dubovska
xorum.io

Software engineer (👩‍💻 native iOS development), Mom 👦, Marathon finisher 🏃🏻‍♀️