Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Casting exceptions when using groups #289

Closed
mradzinski opened this issue Sep 17, 2017 · 7 comments
Closed

Casting exceptions when using groups #289

mradzinski opened this issue Sep 17, 2017 · 7 comments

Comments

@mradzinski
Copy link
Contributor

mradzinski commented Sep 17, 2017

Hi, I have a fairly complex use case where I have 4 kinds of posts (simple message, photo, video, embedded link). All of this posts share in common a header, a text section that's where the user writes down a message and a comments section.

I decided it would be better to separate each section into its own model (so I have HeaderModel_, TextContentModel_, PhotoModel_, VideoModel_, EmbeddedLinkModel_ and CommentsModel_). Each model is created using the @ModelView attribute, inflates a layout and has it's own properties and requirements (the PhotoModel_ for example has a photoUrl property, while the video one has a videoUrl, while the embedded link model has url, previewPhotoUrl, etc).

I was planning on grouping them because all this models need to be put together given the post type and represented inside a CardView, so I have also a class called PostGroup (which extends from EpoxyModelGroup) #where I discuss the kind of post received and given the outcome I build the full post as a group (so, If it's a photo post I would add to the group HeaderModel_, TextContentModel_, PhotoModel_ and CommentsModel_). For this, my group layout has 4 ViewStubs (I always end up adding 4 models regardless of the post).

This approach sadly seems to fail with a ClassCastException stating that, PhotoModel_ can't be casted to VideoModel_, or that VideoModel_ can't be casted to EmbeddedLinkModel_, etc.

Any reasons why this approach might fail? Should I separate each type of post on it's own group even if mostly all of them display the same amount of Models? Each group being added to the adapter has it's own Id (I doubled checked this). If not this one, which approach should I follow to be able to do what I stated above? (grouping posts given its type so they can be rendered inside a CardView).

Here's an shortened example of the group layout I'm using:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <ViewStub
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <ViewStub
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <ViewStub
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <ViewStub
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</LinearLayout>

Thanks!

@elihart
Copy link
Contributor

elihart commented Sep 17, 2017

The problem is that once the view stub is inflated it contains the view for the specific model it was created for (photo vs video). However, when it is recycled it is used with a different content type - then you get the class cast exception.

I've run into something similar. The solution is to use a different layout file for each of your content types. The layout resource value is used as the view type, so having a separate layout forces the view type for each content type to be different, so they are not recycled amongst each other. The layout files can be identical in all but name.

This overhead is unfortunate, but I haven't figured out a better way to allow nested recycled on models within groups - that would be ideal, and also more performant.

Let me know if that doesn't work for you, or if you can think of a better way to solve this!

@mradzinski
Copy link
Contributor Author

That makes total sense to me. I actually ended up creating multiple layouts and also multiple groups (one per view type) so I could assign those layouts to them. It seems a bit repetitive given that mostly each group differs only by one model, but after doing this everything worked just fine.

The overhead is unfortunate indeed, but I think it can be compensated with a proper layout design so that the inflation doesn't take long enough for it to be noticeable. One way to allow nested recycling on models within groups could be to allow manual override of the view type om the group itself, but I don't know how hard that might be from an architecture perspective.

@elihart
Copy link
Contributor

elihart commented Sep 18, 2017

@mradzinski You might not need a separate group class. In my case I use one group and assign the layout dynamically based on type

class WLVotingWrapperModel extends EpoxyModelGroup {

    WLVotingWrapperModel(WishListItem item, WLVotingRowModel_ votingRowModel, EpoxyModel<?> cardModel) {
        super(0, cardModel, votingRowModel);

        switch (item.getItemType()) {
            case Home:
                layout(R.layout.model_wl_voting_wrapper_home);
                break;
            case Place:
            case PlaceActivity:
                votingRowModel.gridMode(true);
                layout(R.layout.model_wl_voting_wrapper_place);
                break;
            case Trip:
                votingRowModel.gridMode(true);
                layout(R.layout.model_wl_voting_wrapper_trip);
                break;
            default:
                throw new IllegalStateException("Unknown type: " + item.getItemType());
        }
    }
}

You actually can override view type already with the getViewType method. If you want you could just return a unique id there based on your item type and use the same layout file for everything.

The nested recycling would be a bit trickier. I think it would require something like this:

  1. Be able to mark a model within a group as recyclable
  2. Remove recyclable views from the model viewgroup when the group is recycled
  3. Have a custom view pool to recycle those views into
  4. When a group is rebound, for each recyclable model, get a view from the pool or create a new one
  5. insert the recycled view back into the model viewgroup at the correct location (accounting for potential nested view groups

This seems very doable, but definitely some complexity. We only use groups in one place so I haven't prioritized it. It would be cool though, and I'm open to pull requests!

@mradzinski
Copy link
Contributor Author

@elihart In my current scenario I actually don't mind having one class per group. I sort of believe it helps separating even more the logic of the adapter and makes more visible the kind of posts to the rest of the team (just a matter of checking which group classes are available to know which kind of posts are in existence instead of going through a switch which might cause some confusion if some groups work that way and some doesn't). And well, since groups aren't annotated/processed then no noticeable overhead happens at compile time.

Anyway, I'll check on what you said regarding nested recycling, can't promise a PR right away (my bandwidth now a days has been limited to code, sleep, repeat), but I'll see what I can do.

@elihart
Copy link
Contributor

elihart commented Jan 31, 2018

I would like to on this and #384 soon

@greensky92
Copy link

Hello,

Im also having ClassCastException: EpoxyGroup cannot be cast to EpoxyModel_() when using model click listener for a model inside a group model.
Ive grouped 3 epoxy models, only one of the models can be visible at a time (toggled using hide/show). To update the visibility of the models, i need to have the position of the item to update the data, i followed your sample.

Now i kinda get why this is having a ClassCastException since epoxy groups are models but is there any way to stop the group's model click from triggering?

@elihart
Copy link
Contributor

elihart commented Jan 22, 2019

This is finally fixed with #657 !

@elihart elihart closed this as completed Jan 22, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants