Occasional ClassCastExceptions occurring with conditionally shown views
See original GitHub issueEpoxy Version - 3.11.0
We’ve noticed a handful of ClassCastException
s occurring. Over the course of a day with at least a thousand visitors to a screen, we’re seeing one, maybe two crashes on our fragment. The crashes all look like the following stacktrace.
Fatal Exception: java.lang.ClassCastException
com.x.y.z.ItemDivider_.handlePreBind (ItemDivider_.java:22)
com.airbnb.epoxy.EpoxyViewHolder.bind (EpoxyViewHolder.java:53)
com.airbnb.epoxy.BaseEpoxyAdapter.onBindViewHolder (BaseEpoxyAdapter.java:104)
com.airbnb.epoxy.BaseEpoxyAdapter.onBindViewHolder (BaseEpoxyAdapter.java:19)
androidx.recyclerview.widget.RecyclerView$Adapter.bindViewHolder (RecyclerView.java:7107)
androidx.recyclerview.widget.RecyclerView$Recycler.tryBindViewHolderByDeadline (RecyclerView.java:6012)
androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline (RecyclerView.java:6279)
androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition (RecyclerView.java:6118)
androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition (RecyclerView.java:6114)
androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next (LinearLayoutManager.java:2303)
androidx.recyclerview.widget.LinearLayoutManager.layoutChunk (LinearLayoutManager.java:1627)
androidx.recyclerview.widget.LinearLayoutManager.fill (LinearLayoutManager.java:1587)
androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren (LinearLayoutManager.java:665)
androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2 (RecyclerView.java:4134)
androidx.recyclerview.widget.RecyclerView.onMeasure (RecyclerView.java:3540)
The EpoxyModel in question
@EpoxyModelClass(layout = R.layout.view_divider)
abstract class ItemDivider : EpoxyModelWithHolder<ItemDivider.DividerHolder>() {
class DividerHolder : KotlinEpoxyHolder()
}
Its layout
<?xml version="1.0" encoding="utf-8"?>
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_1"
android:background="@drawable/linear_layout_divider_vertical" />
The crashes are happening so far are occurring on four different views on the same screen. The other four views also vary in their complexity (non-empty holders, layouts containing a variety of child elements, etc). The only commonality between the views that causing these fatal exceptions is that they are all conditionally shown in the EpoxyRecyclerView
.
For example, the following is a trimmed down version of what our code looks like. The state parameter to the function in this case is a data class contained a variety of fields which are the substates of the screen which get rendered out. The state comes from an rx stream in a ViewModel
, and the substates all result from various network calls which update and emit a new state, with one relevant piece of the total state changed. Some substates are data classes, others are sealed classes. The ClassCastException
s indicate that Models which are being conditionally shown, like the ItemDivider
model, is trying to be cast to a view that immediately follows / precedes it in the list.
private fun buildLayout(state: MyScreenViewState) {
binding.epoxyRecyclerView.withModels {
if (state.showProgress) {
progressView { id("PROGRESS_INDICATOR") }
} else {
when (state.userState) {
UserState.Hidden, is UserState.Visible -> {
welcomeHeaderView {
id("WELCOME_HEADER")
userState(state.userState)
}
}
UserState.Anonymous -> {
signInView {
id("SIGN_IN_VIEW")
}
}
}
if (state.BannerState is BannerState.Visible) {
announcementBannerView {
id("ANNOUNCEMENT_BANNER")
viewState(state.BannerState)
}
if (state.userDetailState !is UserDetailState.Hidden) {
itemDivider { id("DETAIL_TOP_DIVIDER") }
}
if (state.userState is UserState.Visible) {
userView {
id("USER_VIEW")
userState(state.userState)
}
leftInsetDivider { id("DETAIL_BOTTOM_DIVIDER") }
}
spacer { id("SPACER_TWO") }
if (state.userState is UserState.Visible) {
paymentDetailsView {
id("PAYMENT_DETAILS")
}
spacer { id("SPACER_FOUR") }
}
}
}
}
One final thing we’ve noticed from logs on BugSnag, is that crashes tend to happen when the fragment and activity its in pause & stop or start & resume. Also, I’ve been unable to reproduce these crashes locally when following user breadcrumbs to replicate what they were doing when the crash occurred. We’ve used epoxy extensively within our app without any similar crashes. Any thoughts to what could be going wrong here?
Issue Analytics
- State:
- Created 3 years ago
- Reactions:1
- Comments:5 (2 by maintainers)
Thanks for the suggestions. I’ll delve into RecyclerView further to see if i can pinpoint anything. I’ve yet to reproduce this locally unfortunately. I have modified our screen to lift the visibility toggling logic out of the EpoxyModels and into the the Controller. I’ll post a followup when we update our app if that change fixes things or not.
I’m going to (finally) close this issue out, as it seems to be fixed now. While I was never able to reproduce this locally, it appears that the cause of the crash was from two of our
EpoxyModel
s toggling their visibility. For some context, we had someViewPager
s that would show cards to our users, typically 1 to 3 cards at a time. Based on some logging I collected of the screen state at the time, it appeared that the state of the cards was changing right as the crash occurred. TheViewPager
s are wrapped into their own components which manage the appearance of theViewPager
differently when there’s either a single card, or multiple. It appears that these card pagers were setting their root visibility to gone when all of the cards were dismissed by the user.The solution I used (since we weren’t rebuilding these card pagers in epoxy) was to wrap their layout in a
LinearLayout
so that theEpoxtModel
wasn’t technically being set toGONE
and I also put in some better state handling code to prevent EpoxyModels from being added to theEpoxyRecyclerView
if no cards were being shown. These two things combined, seem to have resolved this crash we were seeing.