Rowing the boat — Illustration by Marta Pucci

Build complex screens faster on Android⚡ — Our journey with Epoxy

Akshay Chordiya
Bleeding Edge
Published in
6 min readDec 30, 2019

--

RecyclerView is such an important UI component on Android. But using it can get complex and error prone—especially when dealing with different view types and pagination. Moreover, it’s hard when working on the same screen within the team.

In this article, I’m going to share basics of Epoxy and an overview of the problems it helped us solve. Here’s our journey of how we adopted Epoxy at Clue to build our UIs faster in parallel within the team.⚡

Backstory

It all started with our users, who wanted a more understandable and interactive view of their historical tracking data. ✨(For Clue users, this is generally their menstrual cycles & symptoms.)

So our awesome design team came up with this beautiful intuitive design 😍:

Enhanced Analysis screen in Clue app

I know what you’re thinking already: that’s a lot of view types. And we were definitely concerned about the performance ️and responsiveness of the screen.

That’s when we decided to see if we could use any third-party library to help us with our goal.

Epoxy

Epoxy is a library from Airbnb which takes a declarative / composable approach to build the UI—typically, a list.

  • It uses RecyclerView under the hood
  • It simplifies creation of static and dynamic layouts

Each item in the list is associated to an epoxy model which defines the item’s layout and manages binding the data and releasing the view’s resources when it’s recycled.

Item <-> Epoxy Model <-> Layout resource

The order of the items shown on the UI is based on how you add the model to the adapter or controller.

Philosophy

Epoxy encourages a usage pattern similar to the popular MVVM and MVI architecture where the data flows in one direction (UDF - uni-directional data flow).

Flow with Epoxy

The data forms the state of the UI then goes to EpoxyModel and to the views on the RecyclerView .

The Epoxy Model behaves like a ViewModel and provides the interface between the data and the view. This way the view cannot directly modify the EpoxyModel it gives a callback to modify the state.

Components

There are 2 key components or building blocks in Epoxy:

EpoxyModel

Every item type is represented by an EpoxyModel, which controls binding of data and how to display the view on the UI. These models are mapped to your custom views or xml layouts.

It’s just like ViewModel that is an immutable class which works as an interface between data and the view.

For example, here’s how a model representing a header will look:

@EpoxyModelClass(layout = R.layout.header)
abstract class HeaderModel : EpoxyModelWithHolder<HeaderModel.ViewHolder>() {

@EpoxyAttribute
lateinit var text: String

override fun bind(holder: ViewHolder) {
holder.textView.text = text
}

class ViewHolder : BaseEpoxyHolder() {
val textView: TextView by bind(R.id.header_text)
}
}

EpoxyController / EpoxyAdapter

EpoxyController or EpoxyAdapter implementation contains declaration of all the models to show on the RecyclerView.

class SampleController() : TypedEpoxyController<T>() {
override fun buildModels(data: <T>) {
header {
id("averages")
text("Your cycle data averages")
}
}
}

Epoxy uses annotation processing (yes, I know it adds build time—but it’s worth it until they move to a different approach, or Jetpack Compose arrives), so we can leverage DSL style pattern to create our models.

Without DSL style:

override fun buildModels(data: <T>) {
HeaderModel_() {
.id("averages")
.text("Your cycle data averages")
.addTo(this);
}
}

Tip 💡 — You can subclass EpoxyController but I prefer TypedEpoxyController since it adds more type safety

Why Epoxy?

We didn’t compare all the libraries out there, we instantly liked Epoxy mainly due to 3 key things:

  1. The number of stars 🌟 to the repository and active development
  2. Detailed documentation
  3. Syntax and Kotlin support

Our journey

Most of the advantages which we leveraged were because of the loose coupling between the view types and Epoxy model.

Working in a team 👨‍💻🔀👩‍💻

Generally with Epoxy each view type is associated with it’s own model class and layout resource which reduces the coupling with other view types and increases reusability ⬆️

View Type <-> Epoxy Model <-> Layout resource

This makes it super easy for every team member to build the model and its layout independently, hence in parallel.

For example,
One member of the team works on the header model and other one works on cycle stats model, making it easier and faster to divide the work among the team.

Evolution of UI 🧬

All the models and their respective layouts are like building blocks, and they have super low coupling—which made it easy to built our UI iteratively. That is: we kept adding new features and fine tuning the UI based on user feedback. Having separate models helped us again to work in parallel and build the UI in blocks.

The side effect of having modular design made it easier to do updates only to the necessary piece, making it easier for maintenance. 🛠

Building the whole screen in blocks 🧱

Epoxy doesn’t just help building a list of items. It can help compose an entire screen.

We built a whole new screen by leveraging Epoxy and the models which normally would be made with a ScrollView and all the basic UI elements, which makes it hard to maintain and evolve over time since it’s less reusable overall.

Symptom details screen in Clue app

All the UI items represented by blue outline in the screenshot are composed together using various different models to hash out the final UI.

Little things 🐣

Epoxy provides little things which turn out to be super useful, like:

  • It sets LinearLayoutManager by default [before, I always used to forget to do that and then keep wondering why my RecyclerView is not showing anything 🤔]
  • Sets clipToPadding to false by default to prevent showing top padding while scrolling
  • Declarative style which makes it easier to visualize the UI; to me this feels a bit like Jetpack Compose
  • Supports automatic diffing + background diffing (optional)
  • Pretty solid documentation and issue tracker

These little things save a lot of time and make a huge difference to the development experience in the end.

Performance 💯

Epoxy is mainly an abstraction on top of RecyclerView with lots of features out of the box, so ideally you should expect the same performance as using RecyclerView without any overhead.

Just make sure to follow the same rules while using RecyclerView and tweak the configuration of “Validating the Epoxy model” in production as mentioned in the wiki to squeeze out more performance. 🏋️‍♂️

The challenges

Our team faced couple of challenges when adopting Epoxy:

  • The learning curve isn’t steep, but there is still a learning curve to use Epoxy which can consume a bit of time especially for new joiners in the team. 📈
  • Stack overflow driven-development isn’t very useful for Epoxy as compared to RecyclerView, so you might need to rely on documentation and understanding the internals of Epoxy to figure out any issues or do something complex, though the issue tracker help sometimes
  • The number of resources and libraries available for RecyclerView are obviously more than Epoxy, which can be an obstacle sometimes. But the silver lining is you can use most of stuff for Epoxy, since it’s more or less an abstraction on RecyclerView .
  • We showed our journey to iOS devs. They wanted to have something similar, but it’s not available for iOS. 🍎 Airbnb has created one for iOS, but it’s not open sourced.

Conclusion

I hope this gives an idea of what Epoxy is like and helps you to understand the possibilities. I also hope our journey makes it easier for you and/or your team to evaluate whether to use it. 🛣

I’d still recommend to spin a sample project with Epoxy to have a first-hand experience and make the final decision.

Our experience in a nutshell 🥜:

  • easy to manage different view types
  • parallelizing within the team
  • meeting complex user design demands like sticky headers, pagination, and more
  • easier maintenance
  • happy developer experience 😇

What’s next?

In future articles, I will:

  • Show how to build an entire screen step by step using Epoxy
  • Show how to do really cool and complex things like sync scrolling or sticky headers with Epoxy
  • Deep dive into internals of Epoxy

--

--

Akshay Chordiya
Bleeding Edge

Google Developer Expert @ Android | Android Engineer @ Clue | Instructor @Caster.IO