相关文章推荐
狂野的橡皮擦  ·  ThreeJS ...·  2 周前    · 
酷酷的柑橘  ·  Android ...·  5 天前    · 
犯傻的沙滩裤  ·  王恒·  3 天前    · 
豁达的红烧肉  ·  can't install pip ...·  1 年前    · 
茫然的雪糕  ·  java遍历嵌套json - ...·  1 年前    · 
Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

I have a RecyclerView adapter that takes care of several view types which I've been using for years. Recently, I found out about ConcatAdapter , but I seem to be running into an issue where it seems to be returning the wrong value for getItemViewType() .

I have a use case where I want to show a header and below it a list of items. I've created a new adapter class for the header, but the list items will continue using my old adapter (that also takes care of several other view types).

So I've concatenated the two adapters:

HeaderAdapter headerAdapter = new HeaderAdapter(context);
PostAdapter postAdapter = new PostAdapter(context, postData);
ConcatAdapter concatAdapter = new ConcatAdapter(headerAdapter, postAdapter);
recyclerView.setAdapter(concatAdapter);

This works fine, but when I go to open the activity, it immediately crashes and gives the error:

java.lang.ClassCastException: com.myapp.android.adapter.PostAdapter$PostViewHolder cannot be cast to com.myapp.android.adapter.PostAdapter$FooterViewHolder

Which makes no sense because I never even added a footer to the recycler view.

My getItemViewType() method in PostAdapter looks something like this:

@Override
public int getItemViewType(int position) {
    if (data.get(position) instanceof Footer) {
        return TYPE_FOOTER;
    } else if (data.get(position) instanceof Post) {
        return TYPE_POST;
    return -1;

And my onBindViewHolder() looks something like this:

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    switch (holder.getItemViewType()) {
        case TYPE_FOOTER:
            FooterViewHolder vh1 = (FooterViewHolder) holder;
            configureFooterViewHolder(vh1, position);
            break;
        case TYPE_POST:
            PostViewHolder vh2 = (PostViewHolder) holder;
            configurePostViewHolder(vh2, position);
            break;
        default:
            break;

But for whatever reason, when I run the debugger, it lands on the line FooterViewHolder vh1 = (FooterViewHolder) holder;, even though I never included a footer as part of the PostAdapters data.

So why is it landing on this? Why would ConcatAdapter cause such strange behaviour in an adapter that handles multiple view types?

When you bind the view holders, you should use RecyclerView.Adapters.getItemViewType(position), not RecyclerView.ViewHolder.getItemViewType():

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    switch (getItemViewType(position)) { // not holder.getItemViewType()
        case TYPE_FOOTER:
            FooterViewHolder vh1 = (FooterViewHolder) holder;
            configureFooterViewHolder(vh1, position);
            break;
        case TYPE_POST:
            PostViewHolder vh2 = (PostViewHolder) holder;
            configurePostViewHolder(vh2, position);
            break;
        default:
            break;

You got an error because the view holder was recycled, it could be any view holder, therefore it threw ClassCastException when you forced to cast it to a wrong class.

Seems to be intentional. After a bit of debugging, I find that the adapter.getItemViewType(localPosition) on this line would be correct, but ConcatAdapter also does an additional "conversion from Local to Global item view types" that I've never configured, but at least it overrides what I originally wanted to do.

So you can't see the local item view type values at all, you just get some randomly assigned index from the ConcatAdapter. As all of it is private, your only option is reflection.

Good luck.

(okay, for a real answer, you can also enable ConcatAdapter.Config(isolateViewTypes = false), ensure that all the item view types are different from one another where they are actually different instead of just returning 0 or return super.getItemViewType(position) or so, and then getItemViewType will not create a local -> global mapping to ensure consistency across the adapters, but rather it will return the local item view types for you)

    val concatAdapter = ConcatAdapter(
        ConcatAdapter.Config.Builder()
            .setIsolateViewTypes(false)
            .build(),
                Hi, I'm getting "IndexOutOfBoundsException" that came from the same line you showed here, do you know how to solve this? Adding setIsolateViewTypes(false) didn't solve it. Thanks
– Ofir A.
                Feb 28, 2022 at 11:10
                @EpicPandaForce I am getting the same error, I do not understand the comment above, I appricate if you give me more detail what you mean -stop mutating the list in place-
– Olkunmustafa
                Jan 17 at 10:53
                it means you don't edit the ArrayList, you wrap the ArrayList in Collections.unmodifiableList() and instead when you need a new one, you create a new ArrayList(oldList) make your edits wrap that in unmodifiable list and pass it to the adapter
– EpicPandaForce
                Jan 17 at 13:31
        

Thanks for contributing an answer to Stack Overflow!

  • Please be sure to answer the question. Provide details and share your research!

But avoid

  • Asking for help, clarification, or responding to other answers.
  • Making statements based on opinion; back them up with references or personal experience.

To learn more, see our tips on writing great answers.