airbnb / epoxy

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

Running startSmoothScroll straight after buildModels #1267

Open vkislicins opened 2 years ago

vkislicins commented 2 years ago

I've got an implementation where a SharedFlow is observed, and on change, the observed model is passed to the Epoxy controller (setData is called). The view models themselves don't rebind, but the view models are built and the diff gets run. My main question is - does something else happen with the RecyclerView, layout manager or the adapter when the controller is building the view models?

The reason I'm asking is that I have some other code initiating some scrolling relatively quickly (on a button pressed) after the setData is called:

val smoothScroller =
    object : LinearSmoothScroller(this.context) {
      override fun getVerticalSnapPreference(): Int = snapMode
    }
  smoothScroller.targetPosition = position
  layoutManager?.startSmoothScroll(smoothScroller)

This scrolling works fine 80% of the time, but for the other 20% it fails to find the next item and begins scrolling through the whole RecyclerView, until finding the right item or giving up in a random position. If I remove my observable and no longer call setData before startSmoothScroll, the bug goes away. If I add a delay (even 50ms works) before initiating the scrolling, the bug goes away. So my theory is that the RecyclerView is doing something that prevents the scroller to operate correctly. I added some additional debugging in the scroller and saw that when everything works file, RecyclerView.SmoothScroller goes through this path of the code:

 if (mTargetView != null) {
                // verify target position
                if (getChildPosition(mTargetView) == mTargetPosition) {
                    onTargetFound(mTargetView, recyclerView.mState, mRecyclingAction);

and when the issue occurs, it falls into:

            if (mRunning) {
                onSeekTargetStep(dx, dy, recyclerView.mState, mRecyclingAction);

Considering I don't see the log message Passed over target position while smooth scrolling., I think mTargetView is null when the scroller attempts to scroll. However I'm not sure why it would get into that state and why a 50ms delay would make any difference.