Epoxy is an Android library for building complex screens in a RecyclerView
Clone or download
eboudrant and elihart Follow-up for visibility tracking : adapter data changes (#575)
* Fix for delete/moved/inserted

* Fix the insert / delete from the adapter

* Fix the moved within the adapter

* Disable logs by default

* remove checkstyle warning

* fix javadoc in annotations

* Optimization on detach event, only data changed require to re-process all children

* Also shift the items between the move from-to. With tests.

* Optimize the data changed flag, will set to true only in case visible data get updated

* Reset visibility item only when the adapter position is valid and changed

* Remove system.out log

* Add explicit clearVisibilityStates() in api so user is in control

* Remove extra `recyclerView.getChildViewHolder(child)`
Latest commit 52eaacb Oct 17, 2018
Failed to load latest commit information.
.idea Strip kotlin metadata annotation from generated classes (#523) Sep 5, 2018
epoxy-adapter Follow-up for visibility tracking : adapter data changes (#575) Oct 17, 2018
epoxy-annotations Follow-up for visibility tracking : adapter data changes (#575) Oct 17, 2018
epoxy-databinding remove app_name and unimportant attr in AndroidManifest (#543) Sep 20, 2018
epoxy-integrationtest Async diffing (#425) Aug 7, 2018
epoxy-litho remove app_name and unimportant attr in AndroidManifest (#543) Sep 20, 2018
epoxy-modelfactory Implements visibility events for EpoxyRecyclerView/EpoxyViewHolder. (#… Oct 12, 2018
epoxy-paging Release 2.18.0 (#550) Sep 26, 2018
epoxy-pagingsample Paged list controller (#533) Sep 21, 2018
epoxy-processor Implements visibility events for EpoxyRecyclerView/EpoxyViewHolder. (#… Oct 12, 2018
epoxy-processortest Implements visibility events for EpoxyRecyclerView/EpoxyViewHolder. (#… Oct 12, 2018
epoxy-processortest2 remove allows backups from manifests (#481) Jul 28, 2018
epoxy-sample Release 2.18.0 (#550) Sep 26, 2018
gradle Blacklist setLifecycleOwner method in databinding field parsing, fixes Apr 8, 2018
kotlinsample Implements visibility events for EpoxyRecyclerView/EpoxyViewHolder. (#… Oct 12, 2018
.gitignore Fix Generated Kotlin Extensions Don't Adhere to Constructor Nullabili… Jun 15, 2018
.travis.yml Async improvements (#509) Aug 25, 2018
CHANGELOG.md Release 2.18.0 (#550) Sep 26, 2018
CONTRIBUTING.MD Change to EpoxyController and other improvements (#148) Mar 7, 2017
LICENSE Initial commit Aug 24, 2016
README.md compile -> implementation for recent Gradle support (#541) Sep 18, 2018
RELEASING.md Prepare for 2.16.2 release (#507) Aug 24, 2018
blessedDeps.gradle Paged list controller (#533) Sep 21, 2018
build.gradle Release 2.18.0 (#550) Sep 26, 2018
checkstyle.xml Generate kotlin extension functions for building models (#278) Sep 7, 2017
gradle.properties Release 2.18.0 (#550) Sep 26, 2018
gradlew Prepare 2.2.0 release (#232) Jun 19, 2017
gradlew.bat Gradle refactor issue#49 (#51) Sep 29, 2016
settings.gradle Generate model factory methods (#450) Jun 27, 2018
update_processor_test_resources.rb Add model usage validation in generated code (#161) Mar 21, 2017



Epoxy is an Android library for building complex screens in a RecyclerView. Models are automatically generated from custom views, databinding layouts, or Litho components via annotation processing. These models are then used in an EpoxyController to declare what items to show in the RecyclerView.

This abstracts the boilerplate of view holders, diffing items and binding payload changes, item types, item ids, span counts, and more, in order to simplify building screens with multiple view types. Additionally, Epoxy adds support for saving view state and automatic diffing of item changes.

We developed Epoxy at Airbnb to simplify the process of working with RecyclerViews, and to add the missing functionality we needed. We now use Epoxy for most of the main screens in our app and it has improved our developer experience greatly.


Gradle is the only supported build configuration, so just add the dependency to your project build.gradle file:

dependencies {
  implementation 'com.airbnb.android:epoxy:2.x.y'
  // Add the annotation processor if you are using Epoxy's annotations (recommended)
  annotationProcessor 'com.airbnb.android:epoxy-processor:2.x.y'

Replace x and y with the latest version number: Maven Central

See the releases page for up to date release versions and details


If you are using Kotlin you should also add

apply plugin: 'kotlin-kapt'

kapt {
    correctErrorTypes = true

so that AutoModel annotations work properly. More information here

Also, make sure to use kapt instead of annotationProcessor in your dependencies in the build.gradle file.

Library Projects

To use Epoxy in a library or module add Butterknife's gradle plugin to your buildscript.

buildscript {
  repositories {
  dependencies {
    classpath 'com.jakewharton:butterknife-gradle-plugin:8.7.0'

and then apply it in your module:

apply plugin: 'com.android.library'
apply plugin: 'com.jakewharton.butterknife'

Now make sure you use R2 instead of R inside all Epoxy annotations.

@ModelView(defaultLayout = R2.layout.view_holder_header)
public class HeaderView extends LinearLayout {

Basic Usage

There are two main components of Epoxy:

  1. The EpoxyModels that describe how your views should be displayed in the RecyclerView.
  2. The EpoxyController where the models are used to describe what items to show and with what data.

Creating Models

Epoxy generates models for you based on your view or layout. Generated model classes are suffixed with an underscore (_) are are used directly in your EpoxyController classes.

From Custom Views

Add the @ModelView annotation on a view class. Then, add a "prop" annotation on each setter method to mark it as a property for the model.

@ModelView(autoLayout = Size.MATCH_WIDTH_WRAP_HEIGHT)
public class HeaderView extends LinearLayout {

  ... // Initialization omitted

  public void setTitle(CharSequence text) {

A HeaderViewModel_ is then generated in the same package.

More Details

From DataBinding

If you use Android DataBinding you can simply set up your xml layouts like normal:

<layout xmlns:android="http://schemas.android.com/apk/res/android">
        <variable name="title" type="String" />

        android:text="@{title}" />

Then, create a package-info.java file in any package and add an EpoxyDataBindingLayouts annotation to declare your databinding layouts.

@EpoxyDataBindingLayouts({R.layout.header_view, ... // other layouts })
package com.airbnb.epoxy.sample;

import com.airbnb.epoxy.EpoxyDataBindingLayouts;
import com.airbnb.epoxy.R;

From this layout name Epoxy generates a HeaderViewBindingModel_.

More Details

From ViewHolders

If you use xml layouts without databinding you can create a model class to do the binding.

@EpoxyModelClass(layout = R.layout.header_view)
public abstract class HeaderModel extends EpoxyModelWithHolder<Holder> {
  @EpoxyAttribute String title;

  public void bind(Holder holder) {

  static class Holder extends BaseEpoxyHolder {
    @BindView(R.id.text) TextView header;

A HeaderModel_ class is generated that subclasses HeaderModel and implements the model details.

More Details

Using your models in a controller

A controller defines what items should be shown in the RecyclerView, by adding the corresponding models in the desired order.

The controller's buildModels method declares which items to show. You are responsible for calling requestModelBuild whenever your data changes, which triggers buildModels to run again. Epoxy tracks changes in the models and automatically binds and updates views.

As an example, our PhotoController shows a header, a list of photos, and a loader (if more photos are being loaded). The controller's setData(photos, loadingMore) method is called whenever photos are loaded, which triggers a call to buildModels so models representing the state of the new data can be built.

public class PhotoController extends Typed2EpoxyController<List<Photo>, Boolean> {
    @AutoModel HeaderModel_ headerModel;
    @AutoModel LoaderModel_ loaderModel;

    protected void buildModels(List<Photo> photos, Boolean loadingMore) {
          .title("My Photos")
          .description("My album description!")

      for (Photo photo : photos) {
        new PhotoModel()

          .addIf(loadingMore, this);

Or with Kotlin

An extension function is generated for each model so we can write this:

class PhotoController : Typed2EpoxyController<List<Photo>, Boolean>() {

    override fun buildModels(photos: List<Photo>, loadingMore: Boolean) {
        header {
            title("My Photos")
            description("My album description!")

        photos.forEach {
            photoView {

        if (loadingMore) loaderView { id("loader") }

Integrating with RecyclerView

Get the backing adapter off the EpoxyController to set up your RecyclerView:

MyController controller = new MyController();

// Request a model build whenever your data changes

// Or if you are using a TypedEpoxyController

If you are using the EpoxyRecyclerView integration is easier.

epoxyRecyclerView.setControllerAndBuildModels(new MyController());

// Request a model build on the recyclerview when data changes


Or use Kotlin Extensions to simplify further and remove the need for a controller class.

epoxyRecyclerView.withModels {
        header {
            title("My Photos")
            description("My album description!")

        photos.forEach {
            photoView {

        if (loadingMore) loaderView { id("loader") }

More Reading

And that's it! The controller's declarative style makes it very easy to visualize what the RecyclerView will look like, even when many different view types or items are used. Epoxy handles everything else. If a view only partially changes, such as the description, only that new value is set on the view, so the system is very efficient

Epoxy handles much more than these basics, and is highly configurable. See the wiki for in depth documentation.


See examples and browse complete documentation at the Epoxy Wiki

If you still have questions, feel free to create a new issue.


We support a minimum SDK of 14. However, Epoxy is based on the v7 support libraries so it should work with lower versions if you care to override the min sdk level in the manifest.

If you are using the optional Litho integration then the min SDK is 15 due to Litho's SDK requirement.


Pull requests are welcome! We'd love help improving this library. Feel free to browse through open issues to look for things that need work. If you have a feature request or bug, please open a new issue so we can track it.


Copyright 2016 Airbnb, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at


Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
See the License for the specific language governing permissions and
limitations under the License.