I am building a chat app, where i am using a RecyclerView with an adapter and it is using different viewholders, because the server sends Rich Content which require different views graphs etc.
When i am normally using the app and chat with the server, asking for different content which means different viewholders everything works fine. The Top level adapter is casting the main viewholder to the one i need every time i send a different request.
After a long chat and different viewholders generated and inflated, if i scroll fast, the RecyclerView, up and down, the main adapter is re-rendering the items and it will crash with a classcastException, in cases that earlier, while chatting, it didnt crash.
Here is an example:
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext());
//depending the type of rich content i get from the server i inflate the respective xml
switch (MessageType.values()[viewType]) {
case 1:
View plainTextView = inflater....
return new ViewHolder1(plainTextView);
case 2:
View secondView = inflater...
return new ViewHolder2(secondView);
case 3:
View thirdView = inflater...
return new ViewHolder3(verticalView);
}
return null;
}
Then in the onBindViewHolder i cast the ViewHolder depending on the case of the onCreateViewHolder
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, final int position) {
[...]
case 2:
final ViewHolder2 holder = (ViewHolder2) holder;
//do stuff
break;
The error
2020-01-20 13:22:57.322 E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.----.app, PID: 28353
java.lang.ClassCastException: com.-----.ui.adapters.MainCarouselAdapter$CarouselViewHolder cannot be cast to com.----.ui.adapters.QuickRepliesAdapter$OptionsViewHolder
at com.-----.ui.adapters.QuickRepliesAdapter.onBindViewHolder(QuickRepliesAdapter.java:22)
at androidx.recyclerview.widget.RecyclerView$Adapter.onBindViewHolder(RecyclerView.java:7065)
at androidx.recyclerview.widget.RecyclerView$Adapter.bindViewHolder(RecyclerView.java:7107)
at androidx.recyclerview.widget.RecyclerView$Recycler.tryBindViewHolderByDeadline(RecyclerView.java:6012)
at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6279)
at androidx.recyclerview.widget.GapWorker.prefetchPositionWithDeadline(GapWorker.java:288)
at androidx.recyclerview.widget.GapWorker.prefetchInnerRecyclerViewWithDeadline(GapWorker.java:335)
at androidx.recyclerview.widget.GapWorker.flushTaskWithDeadline(GapWorker.java:351)
at androidx.recyclerview.widget.GapWorker.flushTasksWithDeadline(GapWorker.java:361)
at androidx.recyclerview.widget.GapWorker.prefetch(GapWorker.java:368)
at androidx.recyclerview.widget.GapWorker.run(GapWorker.java:399)
at android.os.Handler.handleCallback(Handler.java:907)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:216)
at android.app.ActivityThread.main(ActivityThread.java:7625)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:524)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:987)
For anyone facing something similar, i had a shared viewPool for my main and sub RecyclerViews.
Recycled view pools allow multiple RecyclerViews to share a common pool of scrape views. This can be useful if you have multiple RecyclerViews with adapters that use the same view types, for example if you have several data sets with the same kinds of item views displayed by a ViewPager.
I dont have the same ViewTypes in my inflations so i found out that it had to leave.
When removing it the bug stopped existing. In addition i fixed my "naming" in the different xml files that where inflated depending the ViewHolder that would be called. I was using the same names for some common TextViews etc.