Git Product home page Git Product logo

epoxy's Introduction

Build Status Maven Central GitHub license GitHub contributors

Epoxy

Epoxy is an Android library for building complex screens in a RecyclerView. Models are automatically generated from custom views or databinding layouts 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.

Installation

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

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"
}

Replace the variable $epoxyVersion with the latest version : Maven Central

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

Kotlin

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

If you are using layout resources in Epoxy annotations then for library projects add Butterknife's gradle plugin to your buildscript.

buildscript {
  repositories {
    mavenCentral()
   }
  dependencies {
    classpath 'com.jakewharton:butterknife-gradle-plugin:10.1.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 {
   ....
}

This is not necessary if you don't use resources as annotation parameters, such as with custom view models.

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 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

  @TextProp
  public void setTitle(CharSequence text) {
    titleView.setText(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">
    <data>
        <variable name="title" type="String" />
    </data>

    <TextView
        android:layout_width="120dp"
        android:layout_height="40dp"
        android:text="@{title}" />
</layout>

Then, create an interface or class in any package and add an EpoxyDataBindingLayouts annotation to declare your databinding layouts.

package com.airbnb.epoxy.sample;

import com.airbnb.epoxy.EpoxyDataBindingLayouts;

@EpoxyDataBindingLayouts({R.layout.header_view, ... // other layouts })
interface EpoxyConfig {}

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;

  @Override
  public void bind(Holder holder) {
    holder.header.setText(title);
  }

  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;

    @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);
    }
  }

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 {
            id("header")
            title("My Photos")
            description("My album description!")
        }

        photos.forEach {
            photoView {
                id(it.id())
                url(it.url())
            }
        }

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

Integrating with RecyclerView

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

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);

If you are using the EpoxyRecyclerView integration is easier.

epoxyRecyclerView.setControllerAndBuildModels(new MyController());

// Request a model build on the recyclerview when data changes
epoxyRecyclerView.requestModelBuild();

Kotlin

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

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

        photos.forEach {
            photoView {
                id(it.id())
                url(it.url())
            }
        }

        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.

Documentation

See examples and browse complete documentation at the Epoxy Wiki

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

Min SDK

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.

Contributing

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.

License

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

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

epoxy's People

Contributors

akshaychordiya avatar anhanh11001 avatar baoti avatar billcarsonfr avatar blah1234 avatar chibatching avatar chrisbanes avatar eboudrant avatar elihart avatar ethan1983 avatar friederbluemle avatar geralt-encore avatar glureau-betclic avatar gpeal avatar martinbonnin avatar meggamind avatar ngsilverman avatar niccorder avatar plnice avatar pmecho avatar rashadsookram avatar rohiththammaiah avatar romankivalin avatar rossbacher avatar seanghay avatar shaishavgandhi avatar subhrajyotisen avatar teemu-rossi avatar tomoima525 avatar vinaygaba avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

epoxy's Issues

Support for minSdkVersion 15

Is there any particular reason why Epoxy supports SDK from API 16?
A lot of projects still have minSdkVersion set to 15, same for default project created in Android Studio.

position

How do I know where I click on Model in recycleView

Generate toString method on model classes

I'm thinking this could be useful for debugging, at times where a model is logged and its toString implementation gives details about it. If we generate an implementation that lists all the attributes it could make logs more useful.

Low priority.

Unable to check RecyclerView.NO_POSITION

As per RecyclerView Animations and Behind the Scenes (Android Dev Summit 2015)
https://youtu.be/imsr8NrIAMs?t=36m2s you should check the ViewHolder's position before triggering an onItemClick that was pass in Model and how to properly handle item click is this correct way ??

public class ButtonModel extends EpoxyModelWithHolder<ButtonModel.ButtonHolder> {

@EpoxyAttribute
String text;
@EpoxyAttribute(hash = false)
ItemClickListener itemClickListener;



@Override
protected int getDefaultLayout() {
    return R.layout.model_button;
}


@Override
public void bind(final ButtonHolder holder) {
    super.bind(holder);

    holder.container.setBackgroundResource(R.color.pink_200);
    holder.container.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            itemClickListener.itemClick(holder.container, text);
        }
    });
    holder.text.setText(text);


}


@Override
public int getSpanSize(int totalSpanCount, int position, int itemCount) {
    return 1;
}

@Override
protected ButtonHolder createNewHolder() {
    return new ButtonHolder();
}


static class ButtonHolder extends BaseEpoxyHolder {

    @BindView(R.id.text)
    TextView text;
    @BindView(R.id.container)
    FrameLayout container;
}

  public interface ItemClickListener {
      void itemClick(View parent, String text);
   }
  }

EpoxyAttribute generate problem for Kotlin

I try to apply EpoxyAttribute to field in Kotlin, like below:

@EpoxyModelClass(layout = R.layout.model_epoxy)
abstract class TestEpoxyModel(@EpoxyAttribute @ColorInt var color: Int) : EpoxyModel<View>() {
	override fun bind(view: View?) {
		view?.setBackgroundColor(color)
	}
}

And got error on compile annotation, like below:

com.airbnb.epoxy.EpoxyProcessorException: EpoxyAttribute annotations must not be on private or static fields. (class: TestEpoxyModel, field: color)
e: Some error(s) occurred while processing annotations. Please see the error messages above.

Consecutive hidden models in a grid layout manager breaks

Once you scroll past the hidden models you can't scroll back up. Hidden models have a span size of 0, which normally seems to work fine, but I found that for a total span count of 2, where two consecutive models are hidden, the recyclerview just breaks and won't scroll up past the hidden models.

Running tests for EpoxyProcessor

Hey! I was really interested in a way that you guys used for testing annotation processor. I tried to run tests on my local machine and all tests failed because of java.lang.IllegalArgumentException: resource BasicModelWithAttribute.java not found. and etc. Am I missing something?

Scroll issue with nested RecyclerView

I have root RecyclerView that's used with Epoxy, then in one of my Epoxy models I have another RecyclerView (also vertical) where I use a normal RecyclerView.Adapter. Whenever the page loads, the root RecyclerView with the Epoxy adapter scrolls down until the nested RecyclerView is visible.

When I switch nested RecyclerView to having a horizontal LinearLayoutManager, this issue doesn't happen. Have you seen this issue before? When I've tried using them in the past, I've usually had scrolling/sizing issues but those seem to work fine when using Epoxy, the only part that seems to have an issue is that it automatically scrolls into view.

I'll share some more details in case you might be able to suggest an alternative implementation. I'm building something similar to this:

I have the root RecyclerView which adds multiple SettingsContainer epoxy models. SettingsContainer has a title TextView and a RecyclerView that contains the settings items inside it.

Clarify bind/unbind behavior.

I observed that unbind is not always called before a view is rebound to another model (recycled). I assume this is working as intended and not a bug. However I think the documentation on this behavior could be improved.

Knowing the described behavior I can interpret this information into the documentation:

/**
* Binds the current data to the given view. You should bind all fields including unset/empty
* fields to ensure proper recycling.
*/
public void bind(T view) {
}
/**
* Similar to {@link #bind(Object)}, but provides a non null, non empty list of payloads
* describing what changed. This is the payloads list specified in the adapter's notifyItemChanged
* method. This is a useful optimization to allow you to only change part of a view instead of
* updating the whole thing, which may prevent unnecessary layout calls. If there are no payloads
* then {@link #bind(Object)} is called instead.
*/
public void bind(T view, List<Object> payloads) {
bind(view);
}
/** Subclasses can override this if their view needs to release resources when it's recycled. */
public void unbind(T view) {
}

https://github.com/airbnb/epoxy/blob/c2bdcd29d8d6acce0d47a0a5f7c14b3b4f07d937/README.md

However without this knowledge this was absolutely not obvious for me from the documentation. Especially because of the naming of the two methods bind/unbind and the observed behavior in most cases (most of the time unbind is called before the view is rebound).

Generate a method to reset fields back to defaults

a method like model.reset() could set all annotated fields back to their defaults. This is useful if a model is reused or changed and you don't want any previously set values carrying over to the new usage.

load more feature?

is there any sample that implemented load more when reaching at the end of recyclerView?

Any chance to extend this to React Native?

From my developer experience, this project is tremendous and it resolved alot of problems from memory to module hierarchy issues. As title, are there any chances or thoughts to advance this module or project into a React-native project?

Don't let a model change its ID once it has been bound

This can help prevent a tricky bug where a model id is changed without an accompanying notify call.

Additionally, this would allow us to optimize the diff algorithm since we can check models list size for changes to know if there were removals or additions.

The downside is it would prevent reusing models in something like a model pool for memory optimization. We don't currently do that anywhere, and models are generally fairly small so unless somebody had thousands of them and recreated them often that shouldn't be a big issue.

The pros seem to outweigh the cons.

Thoughts @gpeal ?

Allow for subclasses in other modules in processor

In the annotation processor we look for annotated fields on super classes and bundle them with sub classes. This relies on the super classes being included in the processor - we don't walk up the inheritance tree and explicitly look for super classes with annotations.

This creates a problem where if the super class is in a different module it's annotated fields will not be included in generated subclasses in other modules.

Solution should be to walk up the inheritance tree for each model class found in the processor.

AutoDiffing fails with realm objects.

I was trying out auto diffing with realm objects , but can't seem to get it working .
I used the @EpoxyAttribute annotation to autogenerate hashcodes and helpers.
But after some time debugging it seems that since realm returns different objects but equivalent objects , i got different hashcodes .

I can't override hashcode in my main model , because the generated model still generates hash codes differently from my implentation.

Have generated models implement Parcelable

cc @gpeal

We can generate the parcelable code on models so that they can easily be saved.

If we go this route we may also want to add an easy way for the models list on EpoxyAdapter to be included in saved state.

Add `setter` boolean param to @EpoxyAttribute

It would default to true, but you could change it to false if you don't want to expose the setter.

Something like @EpoxyAttribute(setter=false) int value

The use case is when you want to the change the value internally in the model but don't want external usage to change it.

Just an idea I'm playing around with, needs more thought

Issue in Generating helper classes with @EpoxyAttribute

public class HeaderModel extends EpoxyModel
{
@EpoxyAttribute String title;
@EpoxyAttribute String subtitle;
@EpoxyAttribute String description;
@EpoxyAttribute(hash=false) View.OnClickListener clickListener;

@LayoutRes
public int getDefaultLayout() {
return R.layout.view_model_header;
}

@OverRide
public void bind(HeaderView view) {
view.setTitle(title);
view.setSubtitle(subtitle);
view.setDescription(description);
view.setOnClickListener(clickListener);
}

}

Having added this property @EpoxyAttribute(hash=false) View.OnClickListener clickListener; it still added in hashCode of generated class

EpoxyModelClass subclasses are not generating

Subclasses are not being generated for EpoxyModelClass (picture included below). The EpoxyModelClass is also missing the layout property to be overwritten within our models (@EpoxyModelClass(layout = R.layout.model_button) gives "cannot resolve method layout" error). This is our EpoxyModelClass.java:
image

And this is yours:
image

Generated subclasses are not recognized in SampleAdapter.java.
image

want: int getModelPositionByStableId(long id) in EpoxyAdapter

I want to update models by model id as addModels() will throw "java.lang.IllegalStateException: Two models have the same ID. ID's must be unique! ", can we have this method in EpoxyAdapter?

protected int getModelPositionByStableId(long id) {
    int size = models.size();
    for (int i = 0; i < size; i++) {
        if (models.get(i).id() == id) {
            return i;
        }
    }

    return -1;
}

Annotation processor duplicates vararg parameters incorrectly

If there is a method on a model that takes varargs as an argument (EpoxyModel<?> myMethod(Long... numbers)) and the processor duplicates the method on a generated subclass the generated method will have an array in the parameters instead of varargs (EpoxyModel<?> myMethod(Long[] numbers))

EpoxyAdapter holds reference to view

Currently EpoxyAdapter holds strong reference to views. Does this means that Adapter can't be field in Fragment and should be null'ed on onViewDestroyed? Ref is hold to Views by boundViewHolders

e.g.

Fragment1 {
  val adapter: EpoxyAdapter 

  @override fun onCreateView(...) {
    <..recycler setup..>
  }
}

And I go to next Fragment, then View of Fragment1 is leaked until whole fragment is Destroyed.

Automatic Diffing problem

I use swipeRefreshLayout to update recyclerview for latest changes. However I could't understand how notifyModelsChanged() method works. When the app is loaded, I fetch items from our Realm database then insert them into recyclerview, but when user triggers swipeRefresh I fetch items from server, add new items with models.add() and run notifyModelsChanged(). However, it adds all newcoming items at the end of the list instead of dispatching changes. (My local and remote items are have same id).

public void addPosts(List<PostRealm> posts) {
    for (PostRealm post : posts) {
      final int postType = post.getType();

      if (postType == 0) {
        models.add(createNormalQuestion(post));
      } else if (postType == 1) {
        models.add(createRichQuestion(post));
      } else if (postType == 2) {
        models.add(createBlog(post));
      }
    }

    notifyModelsChanged();
}
public class NormalQuestionModel extends EpoxyModelWithHolder<NormalQuestionModel.QuestionHolder> {

  @EpoxyAttribute PostRealm post;
  @EpoxyAttribute(hash = false) OnProfileClickListener onProfileClickListener;
  @EpoxyAttribute(hash = false) OnShareClickListener onShareClickListener;
  @EpoxyAttribute(hash = false) OnCommentClickListener onCommentClickListener;
  @EpoxyAttribute(hash = false) OnReadMoreClickListener onReadMoreClickListener;
  @EpoxyAttribute(hash = false) OnPostOptionsClickListener onPostOptionsClickListener;
  @EpoxyAttribute(hash = false) OnVoteClickListener onVoteClickListener;

  @Override protected QuestionHolder createNewHolder() {
    return new QuestionHolder();
  }

  @Override protected int getDefaultLayout() {
    return getLayout();
  }

  @Override public void bind(QuestionHolder holder, List<Object> payloads) {
    if (!payloads.isEmpty()) {
      if (payloads.get(0) instanceof PostRealm) {
        PostRealm post = (PostRealm) payloads.get(0);
        holder.voteView.setVotes(post.getVotes());
        holder.voteView.setCurrentStatus(post.getDir());

        long comments = this.post.getComments();
        comments += 1;
        holder.tvComment.setText(CountUtil.format(comments));

        if (!TextUtils.isEmpty(post.getAcceptedCommentId())) {
          this.post.setAcceptedCommentId(post.getAcceptedCommentId());
        }
      }
    } else {
      super.bind(holder, payloads);
    }
  }

  @Override public void bind(QuestionHolder holder) {
    holder.bindDataWithViewHolder(post, isNormal());
    setupClickListeners(holder);
  }

  @Override public void unbind(QuestionHolder holder) {
    clearClickListeners(holder);
  }

  @Override public boolean shouldSaveViewState() {
    return true;
  }

  private void setupClickListeners(QuestionHolder holder) {
    holder.ivUserAvatar.setOnClickListener(
        v -> onProfileClickListener.onProfileClick(v, post.getOwnerId()));

    holder.tvUsername.setOnClickListener(
        v -> onProfileClickListener.onProfileClick(v, post.getOwnerId()));

    if (onReadMoreClickListener != null && post.hasReadMore()) {
      holder.tvContent.setOnClickListener(
          v -> onReadMoreClickListener.onReadMoreClickListener(v, post));
    }

    holder.tvShare.setOnClickListener(v -> onShareClickListener.onShareClick(v, post));

    holder.tvComment.setOnClickListener(
        v -> onCommentClickListener.onCommentClick(v, post.getId(), post.getOwnerId(),
            post.getAcceptedCommentId(), PostType.TYPE_NORMAL));

    holder.ibOptions.setOnClickListener(
        v -> onPostOptionsClickListener.onPostOptionsClick(v, this, post.getId(),
            post.getOwnerId()));

    holder.voteView.setOnVoteEventListener(direction -> {
      post.setDir(direction);
      onVoteClickListener.onVoteClick(post.getId(), direction, OnVoteClickListener.Type.POST);
    });
  }

  private void clearClickListeners(QuestionHolder holder) {
    holder.ivUserAvatar.setOnClickListener(null);
    holder.tvUsername.setOnClickListener(null);
    holder.tvContent.setOnClickListener(null);
    holder.tvShare.setOnClickListener(null);
    holder.tvComment.setOnClickListener(null);
    holder.ibOptions.setOnClickListener(null);
    holder.voteView.setOnVoteEventListener(null);
  }

  private boolean isNormal() {
    return getLayout() == R.layout.item_feed_question_normal;
  }

  public String getItemId() {
    return post.getId();
  }

  static class QuestionHolder extends BaseEpoxyHolder {
    @BindView(R.id.iv_item_feed_user_avatar) ImageView ivUserAvatar;
    @BindView(R.id.tv_item_feed_username) TextView tvUsername;
    @BindView(R.id.tv_item_feed_time) TimeTextView tvTime;
    @BindView(R.id.tv_item_feed_bounty) TextView tvBounty;
    @BindView(R.id.ib_item_feed_options) ImageButton ibOptions;
    @BindView(R.id.tv_item_question_normal_content) TextView tvContent;
    @BindView(R.id.tv_item_feed_share) CompatTextView tvShare;
    @BindView(R.id.tv_item_feed_comment) CompatTextView tvComment;
    @BindView(R.id.tv_item_feed_vote) VoteView voteView;
    @Nullable @BindView(R.id.view_item_question_normal_category) TagView tagView;

    void bindDataWithViewHolder(PostRealm post, boolean isNormal) {
      final Context context = ivUserAvatar.getContext().getApplicationContext();

      Glide.with(context)
          .load(post.getAvatarUrl())
          .bitmapTransform(CropCircleTransformation.getInstance(context))
          .into(ivUserAvatar);

      tvUsername.setText(post.getUsername());
      tvTime.setTimeStamp(post.getCreated().getTime() / 1000);
      tvBounty.setVisibility(post.getBounty() == 0 ? View.GONE : View.VISIBLE);
      tvBounty.setText(String.valueOf(post.getBounty()));
      tvContent.setText(isNormal
          ? ReadMoreUtil.readMoreContent(post.getContent(), 200)
          : post.getContent());
      tvComment.setText(CountUtil.format(post.getComments()));
      voteView.setVotes(post.getVotes());
      voteView.setCurrentStatus(post.getDir());

      if (tagView != null) {
        tagView.setData(post.getCategoryNames());
      }

      tintDrawables();
    }

    private void tintDrawables() {
      DrawableHelper.withContext(tvShare.getContext())
          .withDrawable(tvShare.getCompoundDrawables()[0])
          .withColor(android.R.color.secondary_text_dark)
          .tint();

      DrawableHelper.withContext(tvComment.getContext())
          .withDrawable(tvComment.getCompoundDrawables()[0])
          .withColor(android.R.color.secondary_text_dark)
          .tint();
    }
  }
}

Carousel behavior

Can you please provide a sample for working with carousels? For example, a horizontal RecyclerView inside the vertical RecyclerView. The issue I'm facing is when data is binded to the horizontal view it becomes a bit sluggish on binding.

Thank you

Why SwipeRefreshLayout dont work, when first model is hidden.

I use Epoxy to manage RecycleView inside SwipeRefreshLayout, like below:

 <android.support.v4.widget.SwipeRefreshLayout
      android:id="@+id/srLayout"
      android:layout_width="match_parent"
      android:layout_height="match_parent">

      <android.support.v7.widget.RecyclerView
          android:id="@+id/rvList"
          android:layout_width="match_parent"
          android:layout_height="match_parent"/>
  </android.support.v4.widget.SwipeRefreshLayout>

It is weird when I hide first model of RecycleView by EpoxyModel.hide(), SwipeRefreshLayout doesn't work anymore.
I create a TestProject, https://github.com/fanxu123/EpoxyTest, specially for demonstration.
Please help, thanks.

Support for grouping models

Ideally you can arbitrarily arrange views/models into whatever grouping pattern you want.

I have something working that does this, but would need to spend some time making it more robust before adding it here.

If there is demand for it I can prioritize it.

Have `hash=false` still check for presence

When using hash=false as an attribute param we don't include that field in the model hashcode at all. We can change this to instead include it in the hashcode as a boolean, for whether it is null or not (would not work with primitives).

The idea is that you may not care about the hash (eg anonymous click listeners that all do the same thing), but there should be a difference between having that field be set and having it be null

I guess for primitives we would leave it as is and exclude them, but it doesn't make sense to not hash primitives (that I can think of).

How to get item view from RecyclerView?

I have problems using epoxy, when I've already added my model in EpoxyAdapter and already set it to the RecyclerView. Somehow when I'm using recyclerView.getLayoutManager().findViewByPosition(0), it always return null. I need to get View from the model that I've already added. Please help me, thank you !

Lint rule to suggest hash=false if an attribute has no hashcode

One of the most common problems I see is either not implementing hashcode on an @EopxyAttribute object, or not using hash=false if that object should not have hashcode (like click listeners).

A lint rule could help a ton with this.

Would love any help if someone wants to set this up.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.