airbnb / epoxy

Epoxy is an Android library for building complex screens in a RecyclerView
https://goo.gl/eIK82p
Apache License 2.0
8.5k stars 733 forks source link

ClassCast Exception when using custom view #757

Closed Merka84 closed 5 years ago

Merka84 commented 5 years ago

I am using a custom view to create imageSlider and I receive this error: android.widget.FrameLayout cannot be cast to PACKAGE_NAME.epoxy.CustomImageSlider

Here is my code: layout file: test_layout.xml

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

    <androidx.viewpager.widget.ViewPager
       android:id="@+id/view_pager_layout"
       android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:layout_marginLeft="16dp"
      android:layout_marginRight="16dp"
      android:layout_gravity="start"
      app:tabIndicatorHeight="2dp"
       app:tabMode="scrollable" />
</FrameLayout>

Custom View class:

@ModelView(defaultLayout = R.layout.test_layout)
 class CustomImageSlider : FrameLayout {
    private var viewpager: ViewPager? = null

    private var sliderItems : List<AdapterSliderItemBindingModel_> = Collections.emptyList()

    constructor(context: Context) : super(context) {
    init()
}

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
    init()
}

    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
    init()
}

private fun init() {
   val view = View.inflate(context, R.layout.test_layout, this)
   this.viewpager = view.findViewById(R.id.view_pager_layout)

}

fun bind(items: List<AdapterSliderItemBindingModel_>) {
    this.sliderItems = items
    this.viewpager?.adapter = MyPager()
}

 inner class MyPager : PagerAdapter() {

    override fun instantiateItem(container: ViewGroup, position: Int): Any {
        val adapterProfileListItemBindingModel = sliderItems[position]

        return  adapterProfileListItemBindingModel.layout
    }

     override fun destroyItem(container: ViewGroup, position: Int, view: Any) {
        container.removeView(view as View)
    }

    override fun getCount(): Int {
        return sliderItems.size
    }

    override fun isViewFromObject(view: View, `object`: Any): Boolean {
        return `object` === view
       }

     }

}

Epoxy Model:

@EpoxyModelClass
abstract class EpoxyImageSliderModel : EpoxyModel<CustomImageSlider>() {
   var items: List<AdapterSliderItemBindingModel_>? = null

    override fun getDefaultLayout(): Int {
        return R.layout.test_layout
    }

    override fun bind(customImageSlider:CustomImageSlider) {
        super.bind(customImageSlider)
        customImageSlider.bind(items ?: ArrayList())
   }
}

Epoxy Controller (I have coded this class in java, as I couldn't use @AutoModel in my controller class when I wrote it in kotlin, it threw AutoModel annotations must not be on private or static fields even though the field was public and non static)

 public class MyEpoxyController extends TypedEpoxyController {

   @AutoModel EpoxyImageSliderModel_ sliderModel;
    private long i = 0;

    @Override
     protected void buildModels(Object d) {
        List<BaseModel> data = (List<BaseModel>) d;
        for(BaseModel model: data){
            if(model != null && 
  model.getItem().component1().equals(SliderCollection.class.getSimpleName())) {
                sliderModel = new EpoxyImageSliderModel_();
                List<SliderItme> items = model.getSliderCollection().getSliderItme();
               List<AdapterSliderItemBindingModel_> temp = new ArrayList<>();
                for(SliderItme item: items){
                    AdapterSliderItemBindingModel_ m = new AdapterSliderItemBindingModel_();
                    m.sliderItem(item);
                    m.id(i++);
                    temp.add(m);
                }
                sliderModel.setItems(temp);
                sliderModel.id(i++);
                sliderModel.addTo(this);
            }
        }
    }
}

The AdapterSliderItemBindingModel_ epoxy model is generated from layout file with databinding, and I have used it in EpoxyImageSliderModel

I get this error : android.widget.FrameLayout cannot be cast to PACKAGE_NAME.epoxy.CustomImageSlider. I have checked similar issues and I used @AutoModel to generate ids for models, also I have set ids in controller before adding the model to controller but it didn't help.

Here is the complete stack trace for error:

  java.lang.ClassCastException: android.widget.FrameLayout cannot be cast to PACKAGE_NAME.epoxy.CustomImageSlider
    at PACKAGE_NAME.epoxy.EpoxyImageSliderModel_.handlePreBind(EpoxyImageSliderModel_.java:21)
    at com.airbnb.epoxy.EpoxyViewHolder.bind(EpoxyViewHolder.java:52)
    at com.airbnb.epoxy.BaseEpoxyAdapter.onBindViewHolder(BaseEpoxyAdapter.java:97)
    at com.airbnb.epoxy.EpoxyControllerAdapter.onBindViewHolder(EpoxyControllerAdapter.java:16)
    at com.airbnb.epoxy.BaseEpoxyAdapter.onBindViewHolder(BaseEpoxyAdapter.java:15)
    at androidx.recyclerview.widget.RecyclerView$Adapter.bindViewHolder(RecyclerView.java:6823)
    at androidx.recyclerview.widget.RecyclerView$Recycler.tryBindViewHolderByDeadline(RecyclerView.java:5752)
    at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6019)
    at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5858)
    at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5854)
    at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2230)
    at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1557)
    at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1517)
    at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:612)
    at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:3924)
    at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:3641)
    at androidx.recyclerview.widget.RecyclerView.onLayout(RecyclerView.java:4194)
    at android.view.View.layout(View.java:19590)
    at android.view.ViewGroup.layout(ViewGroup.java:6053)
    at androidx.constraintlayout.widget.ConstraintLayout.onLayout(ConstraintLayout.java:1915)
    at android.view.View.layout(View.java:19590)PACKAGE_NAME
    at android.view.ViewGroup.layout(ViewGroup.java:6053)
    at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
    at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
    at android.view.View.layout(View.java:19590)
    at android.view.ViewGroup.layout(ViewGroup.java:6053)
    at  
androidx.constraintlayout.widget.ConstraintLayout.onLayout(ConstraintLayout.java:1915)
    at android.view.View.layout(View.java:19590)
    at android.view.ViewGroup.layout(ViewGroup.java:6053)
    at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
    at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
    at android.view.View.layout(View.java:19590)
    at android.view.ViewGroup.layout(ViewGroup.java:6053)
    at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1791)
    at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1635)
    at android.widget.LinearLayout.onLayout(LinearLayout.java:1544)
    at android.view.View.layout(View.java:19590)
    at android.view.ViewGroup.layout(ViewGroup.java:6053)
    at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
    at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
    at android.view.View.layout(View.java:19590)
    at android.view.ViewGroup.layout(ViewGroup.java:6053)
    at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1791)
    at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1635)
    at android.widget.LinearLayout.onLayout(LinearLayout.java:1544)
    at android.view.View.layout(View.java:19590)
    at android.view.ViewGroup.layout(ViewGroup.java:6053)
    at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
    at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
    at com.android.internal.policy.DecorView.onLayout(DecorView.java:758)
    at android.view.View.layout(View.java:19590)
    at android.view.ViewGroup.layout(ViewGroup.java:6053)
    at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2484)
    at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2200)
2019-05-28 23:32:56.526 23012-23012/E/AndroidRuntime:     at 
android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1386)
    at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6733)
    at android.view.Choreographer$CallbackRecord.run(Choreographer.java:911)
    at android.view.Choreographer.doCallbacks(Choreographer.java:723)
    at android.view.Choreographer.doFrame(Choreographer.java:658)
    at 
android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:897)
    at android.os.Handler.handleCallback(Handler.java:789)
    at android.os.Handler.dispatchMessage(Handler.java:98)
    at android.os.Looper.loop(Looper.java:164)
    at android.app.ActivityThread.main(ActivityThread.java:6541)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)

@elihart Can you please have a look and give some insight on this error?

Merka84 commented 5 years ago

Can anyone provide some suggestion?

elihart commented 5 years ago

it looks like your problem is this

@ModelView(defaultLayout = R.layout.test_layout)
 class CustomImageSlider

The layout you provide to the @ModelView annotation must have a top level component of CustomImageSlider

Also, I would not recommend using @Automodel - just use kotlin and manually specify the id, this is mentioned in the wiki.

Lastly, I would be wary of setting up your custom Image slider with a viewpager as you have. There are a host of issues with that that Epoxy's provided Carousel is designed to automatically solve. For one thing, your EpoxyImageSliderModel does not clear anything on unbind.

If you need snapping behavior of a Viewpager that can be achieved with custom SnapHelper logic

Merka84 commented 5 years ago

Actually I was trying to use the suggested approach that you were provided for this issue for my problem. I tried it without the @ModelView annotation first (as it was in here) and since it throws exception, I tried to fix it by adding @ModelView and then by adding @Automodel. In the time I was waiting for suggestion I tried many different variations including using CustomImageSlider as top view in the layout file (that you suggested above) and still same error.

I wanted the ViewPager behaviour and I achieved it with SnapHelper. Although as I need autoscroll behaviour in my slider, having it with a custom ViewPager could make its implementation easier. My problem with this costume model is not resolved but as I am not using this approach, I close the issue.

elihart commented 5 years ago

FYI you can setup auto scrolling for a recyclerview pretty easy with something like this

class AutoScrollingController(private val target: Target) {
    private val automationHandler: Handler by lazy { Handler(Looper.getMainLooper()) }
    private var itemIndex: Int = 0
    var isRunning: Boolean = false
        private set
    var isCancelled: Boolean = false
        private set

    interface Target {
        /**
         * Scroll to the given position.
         * @return true if the scroll was successful, false if no more attempts should be made.
         */
        fun scrollToPosition(position: Int): Boolean
    }

    /**
     * Starts auto scrolling, if it hasn't already been started. Scrolling continues until it is canceled. Once [MAX_SCROLL_COUNT] images are
     * scrolled to this loops back to the first image and continues anew.
     */
    fun start() {
        if (isRunning || ANIMATIONS_DISABLED) {
            return
        }

        isCancelled = false
        isRunning = true

        automationHandler.postDelayed(object : Runnable {
            override fun run() {
                itemIndex++
                // Auto scrolling can be cancelled during the target callback
                if (target.scrollToPosition(itemIndex % MAX_SCROLL_COUNT) && !isCancelled) {
                    automationHandler.postDelayed(this, CYCLE_DELAY_MS)
                } else {
                    cancel()
                }
            }
        }, START_DELAY_MS)
    }

    fun cancel() {
        isCancelled = true
        isRunning = false
        automationHandler.removeCallbacksAndMessages(null)
    }
}
vipulasri commented 5 years ago

@elihart How can I apply this AutoScrollingController to RecyclerView?