Epoxy Android Library

repository·master·Indexed 27 days ago

https://github.com/airbnb/epoxy

An Android library designed to simplify building complex RecyclerView screens. Epoxy uses annotation processing to automatically generate models from custom views, DataBinding layouts, or ViewHolders, which are then managed by an EpoxyController to handle diffing, view types, and state saving. It supports Java and Kotlin, with integration options for KAPT and KSP.

Tokens
4K
Snippets
13
Records
18
Agent score
94%

What's inside Epoxy

  1. Create Epoxy models from ViewHolders

    master

    For XML layouts without DataBinding, you can create a model by extending EpoxyModelWithHolder<Holder>. Use @EpoxyModelClass to specify the layout. Define attributes using @EpoxyAttribute and implement the bind(Holder holder) method to map attributes to your views. Epoxy will generate a subclass (e.g., HeaderModel_) that handles the model implementation details.

    @EpoxyModelClass(layout = R.layout.header_view)
    public abstract class HeaderModel extends EpoxyModelWithHolder<Holder> {
      @EpoxyAttribute String title;
    
      @Override
      public void bind(Holder holder) {
        holder.header.setText(title);
      }
    
      static class Holder extends BaseEpoxyHolder {
        @BindView(R.id.text) TextView header;
      }
    }
  2. Create Epoxy models from Custom Views

    master

    You can generate Epoxy models directly from your custom view classes by using the @ModelView annotation. To define properties that can be set on the model, annotate the corresponding setter methods with @TextProp (or other appropriate prop annotations). Epoxy will generate a model class suffixed with an underscore (e.g., HeaderView_) in the same package.

    @ModelView(autoLayout = Size.MATCH_WIDTH_WRAP_HEIGHT)
    public class HeaderView extends LinearLayout {
    
      ... // Initialization omitted
    
      @TextProp
      public void setTitle(CharSequence text) {
        titleView.setText(text);
      }
    }
  3. Configure Epoxy for Library Projects using layout resources

    master

    If your library project uses layout resources within Epoxy annotations, you must apply the Butterknife Gradle plugin. When using resources in annotations, use R2 instead of R to reference layouts.

    // buildscript in root build.gradle
    buildscript {
      repositories {
        mavenCentral()
      }
      dependencies {
        classpath 'com.jakewharton:butterknife-gradle-plugin:10.1.0'
      }
    }
    
    // In your module build.gradle
    apply plugin: 'com.android.library'
    apply plugin: 'com.jakewharton.butterknife'
    @ModelView(defaultLayout = R2.layout.view_holder_header)
    public class HeaderView extends LinearLayout {
       ....
    }
  4. Publish a release to an internal Artifactory repository

    master

    To publish an internal release to an Artifactory repository, configure your local gradle.properties with the following credentials and URLs:

    • ARTIFACTORY_USERNAME
    • ARTIFACTORY_PASSWORD
    • ARTIFACTORY_RELEASE_URL (and optionally ARTIFACTORY_SNAPSHOT_URL for snapshots)

    Then run the following command. You can use the -PdoNotSignRelease=true flag to skip GPG signing, which is useful if you haven't configured a signing key.

    ./gradlew publishAllPublicationsToAirbnbArtifactoryRepository -PdoNotSignRelease=true --no-configuration-cache
  5. Create Epoxy models from DataBinding

    master

    If your project uses Android DataBinding, you can declare your XML layouts for Epoxy by creating an interface or class annotated with @EpoxyDataBindingLayouts. Pass an array of your layout resource IDs to this annotation. Epoxy will then generate a binding model for each layout (e.g., HeaderViewBindingModel_ for R.layout.header_view).

    package com.airbnb.epoxy.sample;
    
    import com.airbnb.epoxy.EpoxyDataBindingLayouts;
    
    @EpoxyDataBindingLayouts({R.layout.header_view, ... // other layouts })
    interface EpoxyConfig {}
  6. Integrate Epoxy with RecyclerView

    master

    To display your models, attach the controller's adapter to a RecyclerView.

    Standard RecyclerView:

    1. Instantiate your controller.
    2. Call recyclerView.setAdapter(controller.getAdapter()).
    3. Call controller.requestModelBuild() or controller.setData(newData) when data changes.

    EpoxyRecyclerView: If using EpoxyRecyclerView, use setControllerAndBuildModels(controller) and call epoxyRecyclerView.requestModelBuild() on data changes. In Kotlin, you can use the withModels extension to define models directly without a separate controller class.

    MyController controller = new MyController();
    recyclerView.setAdapter(controller.getAdapter());
    
    // Request a model build whenever your data changes
    controller.requestModelBuild();
    
    // Or if you are using a TypedEpoxyController
    controller.setData(myData);
  7. Install Epoxy in a Gradle project

    master

    Add the Epoxy dependency to your module's build.gradle file. It is highly recommended to also include the annotation processor to enable automatic model generation from custom views or DataBinding layouts.

    dependencies {
      implementation "com.airbnb.android:epoxy:$epoxyVersion"
      // Add the annotation processor if you are using Epoxy's annotations (recommended)
      annotationProcessor "com.airbnb.android:epoxy-processor:$epoxyVersion"
    }
  8. Install Epoxy with Kotlin and KSP (Recommended)

    master

    KSP (Kotlin Symbol Processing) is faster than KAPT. To use it, add the KSP plugin to your root build.gradle and apply it to your module. Use ksp instead of kapt or annotationProcessor in your dependencies.

    Note: DataBinding models are not supported with KSP because DataBinding requires KAPT. Use KSP for custom views with @ModelView or ViewHolder models.

    // Root build.gradle
    plugins {
        id 'com.google.devtools.ksp' version "$KSP_VERSION" apply false
    }
    
    // Module build.gradle
    plugins {
        id 'com.android.application'
        id 'kotlin-android'
        id 'com.google.devtools.ksp'
    }
    
    dependencies {
        implementation "com.airbnb.android:epoxy:$epoxyVersion"
        ksp "com.airbnb.android:epoxy-processor:$epoxyVersion"
    }
  9. Install Epoxy with Kotlin and KAPT

    master

    If using Kotlin with KAPT, apply the kotlin-kapt plugin and set correctErrorTypes = true to ensure @AutoModel annotations work correctly. Use kapt instead of annotationProcessor in your dependencies block.

    apply plugin: 'kotlin-kapt'
    
    kapt {
        correctErrorTypes = true
    }
    
    dependencies {
        implementation "com.airbnb.android:epoxy:$epoxyVersion"
        kapt "com.airbnb.android:epoxy-processor:$epoxyVersion"
    }
  10. Use Epoxy models in an EpoxyController

    master

    An EpoxyController defines the items to show in a RecyclerView by adding models inside the buildModels method.

    • Java: Use @AutoModel to inject generated model instances into your controller. Call requestModelBuild() whenever your data changes to trigger a rebuild.
    • Kotlin: Epoxy generates extension functions for each model, allowing for a more declarative DSL inside buildModels.
    public class PhotoController extends Typed2EpoxyController<List<Photo>, Boolean> {
        @AutoModel HeaderModel_ headerModel;
        @AutoModel LoaderModel_ loaderModel;
    
        @Override
        protected void buildModels(List<Photo> photos, Boolean loadingMore) {
          headerModel
              .title("My Photos")
              .description("My album description!")
              .addTo(this);
    
          for (Photo photo : photos) {
            new PhotoModel()
               .id(photo.id())
               .url(photo.url())
               .addTo(this);
          }
    
          loaderModel
              .addIf(loadingMore, this);
        }
      }
  11. Configure KSP processor options for Epoxy

    master

    You can fine-tune the Epoxy code generation and validation by configuring ksp arguments in your module's build.gradle.

    ksp {
        // Validation and debugging
        arg("validateEpoxyModelUsage", "true")                // Validate model usage at runtime (default: true)
        arg("logEpoxyTimings", "false")                       // Log annotation processing timings (default: false)
    
        // Code generation options
        arg("epoxyDisableGenerateReset", "false")             // Disable reset() method generation (default: false)
        arg("epoxyDisableGenerateGetters", "false")           // Disable getter generation (default: false)
        arg("epoxyDisableGenerateOverloads", "false")         // Disable builder overload generation (default: false)
        arg("disableEpoxyKotlinExtensionGeneration", "false") // Disable Kotlin extension generation (default: false)
        arg("epoxyDisableDslMarker", "false")                 // Disable DSL marker annotation (default: false)
    
        // Model requirements
        arg("requireHashCodeInEpoxyModels", "false")          // Require hashCode/equals in models (default: false)
        arg("requireAbstractEpoxyModels", "false")            // Require abstract model classes (default: false)
        arg("implicitlyAddAutoModels", "false")                 // Auto-add models to controllers (default: false)
    }