Skip to content

ViewHolder Models

Eli Hart edited this page Oct 19, 2018 · 3 revisions

If you aren't using databinding or custom views you can create an EpoxyModel for a layout using a view holder.

Using View Holders

Create a class that extends EpoxyModelWithHolder. Your model class should be typed with your view holder type.

The view holder class must extend EpoxyHolder; it can be defined anywhere, but a good pattern is to make it a nested class of the model.

@EpoxyModelClass(layout = R.layout.model_button)
public abstract class ButtonModel extends EpoxyModelWithHolder<Holder> {

  // Declare your model properties like this
  @EpoxyAttribute @StringRes int text;
  @EpoxyAttribute(DoNotHash) OnClickListener clickListener;

  @Override
  public void bind(Holder holder) {
    // Implement this to bind the properties to the view
    holder.button.setText(text);
    holder.button.setOnClickListener(clickListener);
  }

  static class Holder extends EpoxyHolder {
    Button button;

    @Override
    protected void bindView(View itemView) {
      button = itemView.findViewById(R.id.button);
    }
  }
}

Upon building the project a ButtonModel_ class will be generated that you can then use in your EpoxyControllers.

The generated model should be instantiated directly, and has a setter for each property:

new ButtonModel_()
   .id(1)
   .text(R.string.my_text)
   .clickListener(() -> // do something);

Some things to note:

  • @EpoxyModelClass(layout = R.layout.model_button) defines the layout that will be inflated.
  • Make your model class is abstract. It should not be used directly - instead, the ButtonModel_ generated class should be instantiated and used in your EpoxyController.
  • Model properties are defined by annotating fields with @EpoxyAttribute. Setters on the generated class will set these fields for us. You should never set these fields directly.
  • The bind method is called when the model is bound to a view, and should be used to set your data on the views in the view holder.
  • The ViewHolder class simply holds a reference to the views. bindView is called once, when the view is inflated.

BaseHolder Pattern

A useful pattern is to create a base class that all view holders in your app can extend. Your base class can use ButterKnife to bind its view so that subclasses don't explicitly need to.

For example your base class may look like this:

public abstract class BaseEpoxyHolder extends EpoxyHolder {
  @CallSuper
  @Override
  protected void bindView(View itemView) {
    ButterKnife.bind(this, itemView);
  }
}

Applying this pattern helps shorten our example model to this:

@EpoxyModelClass(layout = R.layout.model_button)
public abstract class ButtonModel extends EpoxyModelWithHolder<Holder> {
  @EpoxyAttribute @StringRes int text;
  @EpoxyAttribute OnClickListener clickListener;

  @Override
  public void bind(Holder holder) {
    holder.button.setText(text);
    holder.button.setOnClickListener(clickListener);
  }

  static class Holder extends BaseEpoxyHolder {
    @BindView(R.id.button) Button button;
  }
}

Using Kotlin

There are Kotlin specific viewholder patterns provided in the Kotlin section of the wiki