Skip to content

Occasional ClassCastExceptions occurring with conditionally shown views #1041

@christopherniksch

Description

@christopherniksch

Epoxy Version - 3.11.0

We've noticed a handful of ClassCastExceptions 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 ClassCastExceptions 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?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions