KakaoCup / Kakao

Nice and simple DSL for Espresso in Kotlin

Home Page:https://kakaocup.github.io/Kakao/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

KRecyclerView: RecyclerActions::scrollToEnd() throws NullPointerException

cee-dee opened this issue · comments

Steps to reproduce:

  1. Use long-loading RecyclerView items in a standard RecyclerView
  2. call scrollToEnd() in a UI test

Observed Results:

The code

                    val lastView = view.findViewHolderForLayoutPosition(position)!!.itemView
                    view.scrollBy(0, lastView.height)

throws a NullPointerException because lastView is null.

Expected Results:

I expected the RecyclerView just to scroll down to have the last item fully visible.

Relevant Code:

  override fun scrollToEnd() {
      view.perform(object : ViewAction {
          override fun getDescription() = "Scroll RecyclerView to the bottom"

          override fun getConstraints() = ViewMatchers.isAssignableFrom(RecyclerView::class.java)

          override fun perform(controller: UiController, view: View) {
              if (view is RecyclerView) {
                  val position = view.adapter!!.itemCount - 1
                  view.scrollToPosition(position)
                  controller.loopMainThreadUntilIdle()
                  val lastView = view.findViewHolderForLayoutPosition(position)!!.itemView
                  view.scrollBy(0, lastView.height)
                  controller.loopMainThreadUntilIdle()
              }
          }
      })
  }

Workaround:

I've created an extension function to still be able to do what I'd like to do:

fun KRecyclerView.scrollToEndRepeatedly(repetitions: Int) {

    view.perform(
        object : ViewAction {
            override fun getDescription() =
                "Scroll RecyclerView to the bottom"

            override fun getConstraints() =
                ViewMatchers.isAssignableFrom(
                    RecyclerView::class.java
                )

            override fun perform(controller: UiController, view: View) {
                if (view is RecyclerView) {
                    var lastViewFound = false
                    var tryCount = 0
                    do {
                        tryCount++
                        val position = view.adapter!!.itemCount - 1
                        view.scrollToPosition(position)
                        controller.loopMainThreadUntilIdle()
                        val lastView =
                            view.findViewHolderForLayoutPosition(
                                position
                            )
                        lastView?.let {
                            view.scrollBy(0, lastView.itemView.height)
                            lastViewFound = false
                        }
                        controller.loopMainThreadUntilIdle()
                    } while ((!lastViewFound) && (tryCount < repetitions))
                }
            }

        }
    )
}

While this does what it's supposed to do, I think, there must be a better solution using interceptors which fit's more naturally into Kakaos concepts, i.e. making the repetions parameter superfluous.

Same problem

@cee-dee @AlexeyRybakov what do you mean under "long-loading items"?
If it some view what related on async calls - you may use IdleResources for it. Otherwise it those views is "long-loaded" because of device performance it should't be a problem because it will block UI thread.

@Unlimity please leave you comments

If your RecyclerView's adapter and layout manager cannot layout all children in a single layout pass - there is not much Espresso and Kakao can do for you. You either need to optimize your RecyclerView to be able to layout all items in adapter as it is expected by the system, or use your own extension or try/catch with retry blocks in the test itself.
This is a very specific corner case and is not system expected behavior, so I don't see a lot of value into supporting it as part of the library.